<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>\&quot; [] _ () &quot;/</title><generator>teletype.in</generator><description><![CDATA[Всякое об SQL и ETL.]]></description><link>https://teletype.in/@velipre_xella?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/velipre_xella?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/velipre_xella?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Mon, 29 Jun 2026 01:08:12 GMT</pubDate><lastBuildDate>Mon, 29 Jun 2026 01:08:12 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/92UDlYn_Y_8</guid><link>https://teletype.in/@velipre_xella/92UDlYn_Y_8?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/92UDlYn_Y_8?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>PostgreSQL. Рекурсивные CTE (draft).</title><pubDate>Tue, 02 Jun 2026 18:38:20 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/04/ff/04ff1290-7984-4c9d-816d-9814ff631bbf.png"></media:content><category>Postgresql</category><description><![CDATA[<img src="https://img2.teletype.in/files/d3/12/d3121645-f5a2-4509-ba10-1a08dd1d2b4e.png"></img>В заметке приводятся примеры использования рекурсивных CTE в PostgreSQL.]]></description><content:encoded><![CDATA[
  <p id="F9Uj">В заметке приводятся примеры использования рекурсивных CTE в PostgreSQL.</p>
  <p id="S3ko">Рассмотрим элементарный пример рекурсивного CTE, возвращающего последовательность целых чисел от 1 до 10.</p>
  <pre id="efT5" data-lang="sql">with recursive nums(iteration) as
( select 1 -- Якорный элемент</pre>
  <pre id="zSPM" data-lang="sql">  union all
  
  select iteration + 1 --Рекурсивный элемент
  from nums
  where iteration &lt; 10
)
select * from nums
order by iteration</pre>
  <p id="Vg1u">Этот рекурсивный CTE состоит из следующих элементов:</p>
  <p id="hitJ">Якорный элемент - в примере это запрос select 1. В общем случае это запрос, который возвращает корректный результат и вызывается всего один раз.</p>
  <p id="nTSf">Рекурсивный элемент - в примере это запрос, который идёт после union all. Этот запрос содержит ссылку на имя всего CTE (результирующий набор, полученный на предыдущей итерации) - в примере nums. При первом вызове рекурсивного элемента предшествующий результат представлен значениями, которые вернул закрепленный элемент. При каждом следующем вызове имя CTE представляет результат предыдущего выполнения рекурсивного элемента.</p>
  <p id="Ebku">Т.к. между запросами якорного и рекурсивного элемента находится кляуза union all (или union, если по каким-то причинам нужно убрать дубли), очевидно, что оба запроса должны быть совместимы по количеству и типу возвращаемых<br />столбцов.</p>
  <p id="zTgm">Этот пример - генерация числовой последовательности - одно из возможных применений рекурсивного CTE. Но чаще они используются для работы с иерархическими данными.</p>
  <p id="TTiv">Итак, поработаем с иерархическими данными на примере известной схемы SCOTT. Выведем номер, имя и должность всех начальников из таблицы scott.emp для выбранного работника на всех уровнях иерархии. Будем искать для empno = 7369. Скрипты создания таблицы и её наполнения можно взять на моём <a href="https://github.com/edu-acc/stuff_2_blog/tree/main/common_scripts" target="_blank">github</a></p>
  <p id="kGhb"> </p>
  <figure id="k1Ej" class="m_original">
    <img src="https://img2.teletype.in/files/d3/12/d3121645-f5a2-4509-ba10-1a08dd1d2b4e.png" width="868" />
    <figcaption>Данные таблицы scott.emp</figcaption>
  </figure>
  <pre id="kkBD" data-lang="sql">with recursive mgr_list (e_level, empno, ename, job, mgr) as (
select 1, a.empno, a.ename, a.job
, a.mgr
from scott.emp a
where a.empno = 7369</pre>
  <pre id="d9IM" data-lang="sql">union all</pre>
  <pre id="wrZ9" data-lang="sql">select e_level + 1, a.empno, a.ename, a.job
, a.mgr
FROM mgr_list r 
join scott.emp a on r.mgr = a.empno
)</pre>
  <pre id="TWPq" data-lang="sql">select m.*
, a.ename as mgr_name, a.job as mgr_job 
from mgr_list m
join scott.emp a on m.mgr = a.empno</pre>
  <figure id="tg2M" class="m_original">
    <img src="https://img2.teletype.in/files/50/20/50207f56-6d30-4ac5-9c4b-93ca8db9835b.png" width="762" />
    <figcaption>Результаты выборки</figcaption>
  </figure>
  <p id="BEh1">1. В якорной части конструкции WITH этого запроса выбирается только 1 строка из таблицы для работника с номером 7369 (на скрине выше строка с e_level = 1). Эта строка помещается во временную рабочую таблицу и в результирующую таблицу. </p>
  <p id="CyzX"> </p>
  <figure id="lRvA" class="m_original">
    <img src="https://img2.teletype.in/files/90/75/90751b4f-8211-42d2-8118-88881e69a05e.png" width="590" />
    <figcaption>Иллюстрация к пункту 1</figcaption>
  </figure>
  <p id="PDmB">2. Далее в рекурсивной части мы соединяем эту временную рабочую таблицу под алиасом mgr_list с scott.emp. Условие соединения r.mgr = a.empno джойнит строки, в которых позиция работника на следующем уровне иерархии из таблицы scott.emp совпадает с позицией начальника работника на текущем уровне иерархии из датасета mgr_list. На выходе строка с  e_level равным 2 на скрине выше доставляется в результирующую таблицу (там уже 2 записи) и во временную таблицу - там только 1 запись.</p>
  <p id="BBgM">3. На этом шаге содержимое временной рабочей таблицы заменяется содержимым промежуточной таблицы, а затем промежуточная таблица очищается. После чего аналогично пункту 2 получаем строку с  e_level равным 3 на скрине выше.  Она доставляется в результирующую таблицу (там уже 3 записи) и в недавно очищенную временную таблицу - там только 1 запись.</p>
  <p id="5DXp"></p>
  <figure id="sYSP" class="m_original">
    <img src="https://img2.teletype.in/files/50/31/503178d1-d965-453e-9f63-6fc750fa5c62.png" width="726" />
    <figcaption>Иллюстрация к пункту 3</figcaption>
  </figure>
  <p id="Sw2a">4. Повторяем пункты 2 и 3 для работника </p>
  <p id="Szez">Теперь после очистки рабочей таблицы и заполнением её строкой из промежуточной таблицы с данными работника с должностью президент и значением NULL в поле mgr наши итерации заканчиваются (джойн по условию r.mgr = a.empno возвращает пустоту). И все накопленные в результирующей таблице строки доступны внешнему запросу.</p>
  <figure id="JQs2" class="m_original">
    <img src="https://img1.teletype.in/files/00/c7/00c7cad9-328a-47a8-8df4-71929be1e21f.png" width="489" />
  </figure>
  <p id="ulSa">Более подробно по теме можно почитать в зачётной книжке <a href="https://postgrespro.ru/education/books/advancedsql" target="_blank">PostgreSQL. Профессиональный SQL</a></p>
  <p id="ckIO">Оттуда я взял картинки для иллюстрации пунктов 1 и 3. А ещё там много поучительных задачек, которые можно прорешать и потом преисполниться.</p>
  <p id="p0Rg">P.S. Кстати, убедиться в том, что в каждую итерацию в рабочую таблицу приходит ровно 1 строка, можно посмотрев план запроса.</p>
  <figure id="vMym" class="m_original">
    <img src="https://img2.teletype.in/files/1e/47/1e4760c4-b81f-4d1f-b820-1aaa4e5bafe1.png" width="669" />
  </figure>
  <p id="hfth">FIXME пп 3-4 откорректировать</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/OaURe4KZxf2</guid><link>https://teletype.in/@velipre_xella/OaURe4KZxf2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/OaURe4KZxf2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>Clickhouse. Использование ключа типа String в словаре (draft). complex_key_hashed</title><pubDate>Mon, 11 May 2026 06:15:08 GMT</pubDate><description><![CDATA[В заметке рассказывается о том, что делать, если ключ в словаре имеет не числовой тип, а строковый.]]></description><content:encoded><![CDATA[
  <p id="NKmi">В заметке рассказывается о том, что делать, если ключ в словаре имеет не числовой тип, а строковый.</p>
  <p id="Adr7">Существует несколько способов хранения словарей в памяти, каждый из которых имеет свои компромиссы между использованием CPU и RAM (дока Clickhouse). И если - так сложились обстоятельства - ключ словаря это натуральный ключ из источника имеющий строковый тип, а не UInt64, то параметр Layout <em>придётся делать complex_key_hashed</em>. </p>
  <p id="p4xw">Посмотрим на примере из пет-проекта. Есть такой источник</p>
  <figure id="zeLo" class="m_original">
    <img src="https://img1.teletype.in/files/0c/ac/0cac334a-5037-4e23-b253-a675b0124962.png" width="564" />
  </figure>
  <p id="sozD">Словарь создаётся так</p>
  <pre id="rOgW" data-lang="sql">CREATE DICTIONARY portfolio.actual_quotation_dict
(
ticker           String,
quotation        Decimal64(6)
)
PRIMARY KEY ticker
SOURCE(CLICKHOUSE(
	QUERY &#x27;SELECT ticker, quotation FROM portfolio.actual_quotation&#x27;
	USER &#x27;username&#x27;
	PASSWORD &#x27;password&#x27;
	HOST &#x27;localhost&#x27;
    PORT 9000
))
LIFETIME(MIN 600 MAX 900)
LAYOUT(complex_key_hashed())</pre>
  <p id="vmyk">И теперь при использовании словаря вызывая dictGet, нужно обернуть значение из словаря в tuple()</p>
  <pre id="tpIi" data-lang="sql">SELECT instr_short, ticker, qty, quotation
,dictGet(&#x27;portfolio.actual_quotation_dict&#x27;, &#x27;quotation&#x27;, tuple(ticker)) as act_quotation
, equity, balance_datetime
FROM portfolio.portfolio_history</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/83SVMuKRrUB</guid><link>https://teletype.in/@velipre_xella/83SVMuKRrUB?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/83SVMuKRrUB?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>PostgreSQL. Использование USING и RETURNING в команде DELETE.</title><pubDate>Sat, 17 Jan 2026 08:32:44 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/8c/5c/8c5ca736-29c1-4bfd-8b0f-22879ad9f11e.png"></media:content><category>Postgresql</category><description><![CDATA[<img src="https://img3.teletype.in/files/69/a9/69a96456-0cb7-4c07-af6d-50f2cd414149.png"></img>В заметке рассказывается использование предложений USING и RETURNING в команде DELETE.]]></description><content:encoded><![CDATA[
  <p id="hZd9">В заметке рассказывается использование предложений USING и RETURNING в команде DELETE.</p>
  <p id="0Bgj">Воспользуемся известной схемой SCOTT. Допустим, мы хотим удалить из таблицы emp всех работников из департамента с названием SALES.</p>
  <figure id="p8Y5" class="m_original">
    <img src="https://img3.teletype.in/files/69/a9/69a96456-0cb7-4c07-af6d-50f2cd414149.png" width="327" />
    <figcaption>Таблица департаментов dept</figcaption>
  </figure>
  <p id="nb8k">По стандарту эту задачу можно решить так:</p>
  <pre id="ZbOO" data-lang="sql">delete
from scott.emp e
where e.deptno in (select deptno from scott.dept d where d.dname = &#x27;SALES&#x27;);</pre>
  <p id="IwKf"></p>
  <p id="grgr"><code><strong>Но PostgreSQL при удалении позволяет ссылаться на столбцы других таблиц в условии WHERE, когда эти таблицы перечисляются в предложении USING. Таким образом решение указанной задачи с использованием предложения USING выглядит так: </strong></code></p>
  <pre id="RGlY" data-lang="sql">delete
from scott.emp e
using scott.dept d
where e.deptno = d.deptno and d.dname = &#x27;SALES&#x27;;</pre>
  <p id="44F7">По сути, это дилит с джойном. В MS SQL Server можно было бы в лоб написать join (код ниже), а PostgreSQL пошёл своим путём.</p>
  <pre id="aD74" data-lang="sql">-- вариант удаления в ms sql server через join 
-- (но это не точно, пишу по памяти)
delete e
from scott.emp e
join scott.dept d
  on e.deptno = d.deptno and d.dname = &#x27;SALES&#x27;;</pre>
  <p id="P021">В документации пишется, что <em>&quot;В ряде случаев запрос в стиле соединения легче написать и он может работать быстрее, чем в стиле вложенного запроса&quot;. </em>Так это или нет можно убедиться только на практике.</p>
  <p id="66hK">Кстати, чтобы посмотреть реальный план команды DELETE без фактического её выполнения, можно начать транзакцию, выполнить explain analyze запроса, и потом откатить выполнение командой rollback (рисунок ниже).</p>
  <figure id="ccUU" class="m_original">
    <img src="https://img4.teletype.in/files/7f/34/7f348053-cc2f-43f6-b6f9-1ed0f4707f25.png" width="637" />
    <figcaption>Просмотр реального плана выполнения команды с последующим откатом.</figcaption>
  </figure>
  <p id="9xl9">Если бы вдруг перед удалением захотелось посмотреть, какие записи из таблицы удаляются, то  помогла бы кляуза RETURNING.</p>
  <figure id="ftX5" class="m_original">
    <img src="https://img3.teletype.in/files/ad/7f/ad7ff6f4-d489-4a9c-acb9-304148dff22a.png" width="839" />
    <figcaption>Вывод удаляемых записей</figcaption>
  </figure>
  <p id="P9RI"><code>Также RETURNING может использоваться в командах INSERT, UPDATE и MERGE для получения данных из модифицируемых строк. Подробно об этом можно почитать в <a href="https://postgrespro.ru/docs/postgresql/current/dml-returning" target="_blank">документации</a>. </code></p>
  <p id="Y0Br">P.S. А ещё кляуза USING может использоваться в ORDER BY. Если верить документации.</p>
  <figure id="2ecT" class="m_original">
    <img src="https://img4.teletype.in/files/76/f1/76f16160-6c7f-4ada-b62a-3a6d70f2397c.png" width="1108" />
    <figcaption>Синтаксис команды SELECT в доке</figcaption>
  </figure>
  <p id="H7Kp">Но почему-то в статье документации <a href="https://postgrespro.ru/docs/postgresql/current/queries-order" target="_blank">7.5. Сортировка строк (<code>ORDER BY</code>)</a> USING уже отсутствует. Может, плохо искал (</p>
  <figure id="6Inl" class="m_original">
    <img src="https://img2.teletype.in/files/9a/a6/9aa63b73-8f85-4ca7-9ff4-546bc7d43d23.png" width="1349" />
  </figure>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/FeON8JuUovn</guid><link>https://teletype.in/@velipre_xella/FeON8JuUovn?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/FeON8JuUovn?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>Python. Задачи с собесов (draft)</title><pubDate>Mon, 15 Dec 2025 15:19:30 GMT</pubDate><category>Python</category><description><![CDATA[В заметке приведено несколько задач с собесов, которые нужно было решать онлайн. Со временем возможно будут дополнения.]]></description><content:encoded><![CDATA[
  <p id="sYR1">В заметке приведено несколько задач с собесов, которые нужно было решать онлайн. Со временем возможно будут дополнения.</p>
  <p id="PuZS">Задача: Написать функцию, которая считает сумму произведений всех элементов массива, исключая одно на каждом шаге. В массиве только положительные целые числа.</p>
  <p id="kwQ6"># multsum([1,5,6]) = 5*6 + 1*6 + 1*5 = 41</p>
  <p id="MNya"># multsum([1,5,6,7]) = 5*6*7 + 1*6*7 + 1*5*7+ 1*5*6 = 317</p>
  <pre id="3UL0" data-lang="python">import math</pre>
  <pre id="CORY" data-lang="python">def multsum(arr: list):
    total_product = math.prod(arr) # Вычисляем общее произведение всех чисел
    sum_of_excluded_products = 0
    
    for num in arr:
        sum_of_excluded_products += total_product // num
            
    return sum_of_excluded_products</pre>
  <p id="CiUx">Задача: Есть массив целых чисел и число K. Найти два таких (не обязательно различных) числа в массиве, сумма которых равна K, либо вывести, что таких чисел нет.</p>
  <figure id="kaiO" class="m_original">
    <img src="https://img3.teletype.in/files/a5/a9/a5a9ced3-3d04-427f-8660-173eb3ce1a81.png" width="689" />
  </figure>
  <pre id="fN66" data-lang="python">def find_two_sum(nums, K: int):
    &quot;&quot;&quot;
    Находит два числа в массиве, сумма которых равна K.
    
    Args:
        nums: Список целых чисел.
        K: Целевая сумма.
        
    Returns:
        Кортеж из двух чисел, если пара найдена, или строка &quot;null&quot;, если не найдена.
    &quot;&quot;&quot;
    
    # Словарь для хранения чисел, которые мы уже встречали
    seen_numbers = {} 
    
    for num in nums:
        # Вычисляем число, которое дополнит текущее &#x60;num&#x60; до &#x60;K&#x60;
        complement = K - num
        
        # Если &#x60;complement&#x60; уже есть в нашем словаре &#x60;seen_numbers&#x60;,
        # значит, мы нашли нужную пару.
        if complement in seen_numbers:
            return (complement, num)
        
        # Если &#x60;complement&#x60; не найден, добавляем текущее &#x60;num&#x60; в словарь,
        # чтобы его могли найти на следующих шагах.
        # Значение в словаре (например, True или индекс) не имеет значения для этой конкретной задачи,
        # главное, чтобы ключ был добавлен.
        seen_numbers[num] = True 
        
    # Если цикл завершился, и мы не нашли пару, возвращаем &quot;null&quot;
    return &quot;null&quot;</pre>
  <p id="KRb2">Задача: Дана строка из латинских заглавных букв. Необходимо заменить все повторы одинаковых подряд идущих букв на букву + цифру. Одиночные буквы заменять не надо.</p>
  <figure id="X0xG" class="m_original">
    <img src="https://img4.teletype.in/files/77/f8/77f84edf-67fd-4bf0-b854-49d58fed7930.png" width="608" />
  </figure>
  <pre id="lKEF" data-lang="python">def compress_rle(s: str) -&gt; str:
    &quot;&quot;&quot;
    Сжимает строку: для подряд идущих одинаковых символов длины &gt;= 2
    заменяет их на &#x27;символ&#x27; + &#x27;число&#x27;. Одиночные символы не меняет.
    Пример: &quot;AAAABBB CAAA&quot; -&gt; &quot;A4B3C A3&quot; (пробелы сохраняются)
    &quot;&quot;&quot;
    if not s:
        return &quot;&quot;
    
    res = []
    prev = s[0]
    count = 1
    
    for ch in s[1:]:
        if ch == prev:
            count += 1
        else:
            # Завершили блок
            if count == 1:
                res.append(prev)
            else:
                res.append(f&quot;{prev}{count}&quot;)
            prev = ch
            count = 1
    
    # Добавляем последний блок
    if count == 1:
        res.append(prev)
    else:
        res.append(f&quot;{prev}{count}&quot;)
    
    return &quot;&quot;.join(res)</pre>
  <h3 id="8HQD">Задача о «Правильной скобочной последовательности» (Valid Parentheses)</h3>
  <p id="VC4O">Дана строка, состоящая только из символов скобок: &#x27;(&#x27;, &#x27;)&#x27;, &#x27;{&#x27;, &#x27;}&#x27;, &#x27;[&#x27; и &#x27;]&#x27;. <br />Определите, является ли входная строка валидной.<br />Строка считается валидной, если:</p>
  <p id="aUmE">1. Открытые скобки должны быть закрыты скобками того же типа.</p>
  <p id="19qX">2. Открытые скобки должны быть закрыты в правильном порядке.</p>
  <p id="qY0i">3. Каждая закрывающая скобка должна иметь соответствующую ей открывающую скобку того же типа.<br />Примеры:</p>
  <ul id="GD8b">
    <li id="HGX4">Вход: s = &quot;()&quot; — Вывод: True</li>
    <li id="XzRA">Вход: s = &quot;()[]{}&quot; — Вывод: True</li>
    <li id="LPmb">Вход: s = &quot;(]&quot; — Вывод: False</li>
    <li id="RvB0">Вход: s = &quot;([)]&quot; — Вывод: False</li>
    <li id="pINl">Вход: s = &quot;{[]}&quot; — Вывод: True</li>
  </ul>
  <pre id="34pK" data-lang="python">def is_valid(s: str) -&gt; bool:
    # Словарь соответствия закрывающей скобки открывающей
    bracket_map = {
        &quot;)&quot;: &quot;(&quot;,
        &quot;}&quot;: &quot;{&quot;,
        &quot;]&quot;: &quot;[&quot;
    }
    
    # Стек для хранения открывающих скобок
    stack = []
    
    for char in s:
        # Если символ — это закрывающая скобка
        if char in bracket_map:
            # Извлекаем верхний элемент из стека, если он не пуст,
            # иначе присваиваем заглушку (например, &#x27;#&#x27;)
            top_element = stack.pop() if stack else &#x27;#&#x27;
            
            # Если открывающая скобка из стека не совпадает с нужной для этого типа
            if bracket_map[char] != top_element:
                return False
        else:
            # Если символ — открывающая скобка, кладем её в стек
            stack.append(char)
            
    # Если в конце стек пуст — все скобки закрыты корректно
    return not stack</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/yr8gIWgDyW_</guid><link>https://teletype.in/@velipre_xella/yr8gIWgDyW_?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/yr8gIWgDyW_?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>Реализация Data Vault в Pentaho DI. SCD2 для сателлитов.</title><pubDate>Sun, 09 Nov 2025 14:24:13 GMT</pubDate><category>Pentaho DI</category><description><![CDATA[<img src="https://img1.teletype.in/files/00/db/00db48d3-d6da-4598-941e-9e4c76eab9bb.jpeg"></img>В заметке проверяется корректность пайплайна по сбору сателлита и реализации SCD2 при изменении данных на источнике.]]></description><content:encoded><![CDATA[
  <p id="Lrq6">В заметке проверяется корректность пайплайна по сбору сателлита и реализации SCD2 при изменении данных на источнике.</p>
  <p id="Zop4">В <a href="https://teletype.in/@velipre_xella/8mxHXZH3clm" target="_blank">предыдущей заметке</a> мы рассмотрели вариант реализации пайплайна по сбору Data Vault и его первичного заполнения. Но случай с изменением данных в источнике и его обработка в сателлите не был рассмотрен.</p>
  <p id="3qK2">Будем тренироваться на таблице актеров, изменим актерам с ид 1 и 2 имена. До изменения они выглядят так.</p>
  <figure id="dyuh" class="m_original">
    <img src="https://img1.teletype.in/files/00/db/00db48d3-d6da-4598-941e-9e4c76eab9bb.jpeg" width="560" />
    <figcaption>Исходные данные</figcaption>
  </figure>
  <p id="R5s7">Пускай с сегодняшнего дня актёр с именем PENELOPE станет OLEG, а NICK - Petr. Сказано - сделано.</p>
  <pre id="6H4z" data-lang="sql">update sakila.actor set first_name = 
  case actor_id when 1 then &#x27;OLEG&#x27; else &#x27;Petr&#x27; end
where actor_id in (1,2)</pre>
  <p id="6Zjj">Смотрим изменения</p>
  <figure id="asFD" class="m_original">
    <img src="https://img3.teletype.in/files/2d/2b/2d2bd05f-1d10-4e7c-bc26-b16bdf2e029f.png" width="573" />
    <figcaption>Актеры на источнике с новыми именами.</figcaption>
  </figure>
  <p id="5IzF">Выполняем трансформацию sat_actor, у нас должно появиться 2 новые записи в таблице сателлита и 2 старые записи должны закрыться текущей датой. Проверяем.</p>
  <pre id="kiE4" data-lang="sql">select * 
from sakila_data_vault.sat_actor
where hub_actor_id in (1,2)</pre>
  <figure id="Etf6" class="m_original">
    <img src="https://img2.teletype.in/files/93/98/93986190-b6ae-4b9a-88d3-e1e57c9f8bd9.png" width="1152" />
    <figcaption>Сателлит после работы трансформации.</figcaption>
  </figure>
  <p id="7l7p">На рисунке видно, что трансформация отработала корректно. SCD2 присутствует.</p>
  <p id="jbhG">P.S. Если посмотреть код забора данных источника в трансформации, то видно, что каждый раз берутся все данные.</p>
  <figure id="exJh" class="m_original">
    <img src="https://img1.teletype.in/files/4b/20/4b20f336-eda1-4b8c-aa81-c8ecc16cda22.png" width="743" />
    <figcaption>Код в степе, забирающий данные с источника.</figcaption>
  </figure>
  <p id="j9P0">В реальности, конечно, мы бы не стали при последующих запусках тащить все данные источника, а использовали бы в запросе условие-привязку к дате обновления на источнике.</p>
  <pre id="wcEq" data-lang="sql">from actor
where last_update &gt; 
/* тут возможно будет дата предыдущего запуска трансформации
 чтобы взять только инкремент */
order by 1</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/8mxHXZH3clm</guid><link>https://teletype.in/@velipre_xella/8mxHXZH3clm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/8mxHXZH3clm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>Реализация Data Vault в Pentaho DI</title><pubDate>Wed, 22 Oct 2025 19:10:09 GMT</pubDate><category>Pentaho DI</category><description><![CDATA[<img src="https://img4.teletype.in/files/f6/80/f680fc6d-fd86-4b12-83b3-579bd22927a8.jpeg"></img>Это даже не заметка, просто ссылка на главу в древней книге &quot;Pentaho Kettle Solutions&quot; и мои комментарии, как можно этим воспользоваться. Приложена ссылка на sql-код для создания таблиц хабов, линков и сателлитов и файлы джобов и трансформаций Pentaho DI.]]></description><content:encoded><![CDATA[
  <p id="eM8M">Это даже не заметка, просто ссылка на главу в древней книге &quot;Pentaho Kettle Solutions&quot; и мои комментарии, как можно этим воспользоваться. Приложена ссылка на sql-код для создания таблиц хабов, линков и сателлитов и файлы джобов и трансформаций Pentaho DI.</p>
  <figure id="qWUz" class="m_original">
    <img src="https://img3.teletype.in/files/e8/3d/e83d8591-966b-467b-9421-a9e8b33fd9fd.png" width="710" />
  </figure>
  <p id="O6Fg">Итак, глава 19 Data Vault Management.</p>
  <p id="jMlF">Источником является учебная БД Sakila, MySQL. В книге она описывается, но можно и тут про неё прочитать https://dev.mysql.com/doc/sakila/en/.</p>
  <p id="IdCA">Почему-то репозиторий с файлами книги у меня на раз не нагуглился, поэтому выложил примеры к 19 главе на свой гитхаб. Все файлы джобов и трансформаций можно взять <a href="https://github.com/edu-acc/stuff_2_blog/tree/main/Pentaho%20Kettle%20Solutions%20Files%20ch%2019/ch19files" target="_blank">здесь</a></p>
  <p id="DIDl">Там же находится файл sakila_data_vault_schema.sql с DDL формирующим таблицы хабов, линков и сателлитов.</p>
  <p id="ip6f">Основная проблема, на которую я наткнулся при воспроизведении ETL из книги, это незаполненный степ Filter rows в  трансформациях по сбору сателлитов (на картинке ниже)</p>
  <figure id="vx1s" class="m_original">
    <img src="https://img1.teletype.in/files/47/16/471676fb-9e82-4ca9-9a88-1f58b4a413cb.png" width="659" />
    <figcaption>Трансформация по сбору сателлита sat_staff.ktr</figcaption>
  </figure>
  <figure id="Li2m" class="m_original">
    <img src="https://img2.teletype.in/files/d7/3a/d73acdcc-a73f-43ee-a719-bb527a7026c0.png" width="743" />
    <figcaption>Баг со степом.</figcaption>
  </figure>
  <p id="kK0X">Я в паре трансформаций этот баг исправил, и сбор этих сателлитов завёлся.</p>
  <figure id="K6d7" class="m_original">
    <img src="https://img1.teletype.in/files/85/7a/857a0300-fc20-44d3-906b-e5a74a0cc4c1.png" width="732" />
    <figcaption>Степ с заполненным условием</figcaption>
  </figure>
  <p id="S2M9">И, к сожалению, авторы не приложили sql-код для эмуляции изменений данных на источнике. Т.е. доступно только первичное наполнение данных, без инкремента. Так что для проверки корректности ведения SCD2 это тоже нужно будет делать самому.</p>
  <p id="cMkd">После построения хоронилища по Data Vault предлагается натянуть на него star schema. Джобы и трансформации прилагаются, но их работоспособность и корректность я уже не проверял.</p>
  <figure id="oPHn" class="m_original">
    <img src="https://img1.teletype.in/files/45/c0/45c0f587-3589-4af1-8517-cde07a358727.png" width="1008" />
    <figcaption>Файлы репозитория</figcaption>
  </figure>
  <p id="AyNZ">И, кстати, странно, что не был создан финальный джоб, запускающий наполнение Data Vault целиком. Что-то вроде такого</p>
  <figure id="7I1Z" class="m_original">
    <img src="https://img4.teletype.in/files/f9/aa/f9aa65dc-bc8d-478c-b34c-0123246dbea1.png" width="445" />
    <figcaption>Вариант финального джоба, которого в файлах книги нет.</figcaption>
  </figure>
  <p id="WUSg"></p>
  <p id="cNmS">В общем и целом для &quot;потрогать&quot; data vault приемлемый вариант.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/lqSEHvpZSPU</guid><link>https://teletype.in/@velipre_xella/lqSEHvpZSPU?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/lqSEHvpZSPU?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>SQL. Задачи с собесов (draft).</title><pubDate>Wed, 17 Sep 2025 19:54:58 GMT</pubDate><category>Postgresql</category><description><![CDATA[В основном это будут задачи не с собесов, а из тестовых заданий, которые раньше давали на дом. Была такая эпоха, до пришествия ChatGPT и прочих. Но сейчас такие задачи в натуре могут на онлайн кодинге навалить.]]></description><content:encoded><![CDATA[
  <p id="2WHG">Некоторые задачи из подборки не с собесов, а из тестовых заданий, которые раньше давали на дом. Была такая эпоха, до пришествия ChatGPT и прочих. Но сейчас такие задачи на онлайн кодинге наваливают.  Все скрипты приведены для PostgreSQL.</p>
  <p id="PG5q">Заметка будет дополняться со временем.</p>
  <p id="J7W4">1)Имеется таблица курсов валют следующей структуры:</p>
  <pre id="3gTA" data-lang="sql">create table scott.rates(
curr_id int, — ид валюты
date_rate DATE, — дата курса
rate numeric)</pre>
  <figure id="w2Jg" class="m_original">
    <img src="https://img4.teletype.in/files/f8/25/f8258397-5866-4dab-a419-571801583ee8.png" width="385" />
    <figcaption>Исходные данные.</figcaption>
  </figure>
  <p id="uflf">Курс валюты устанавливается не на каждую календарную дату и действует до следующей смены курса</p>
  <p id="rLs1">Уникальный ключ: curr_id + date_rate.</p>
  <p id="tRYj">Напишите запрос, который покажет действующее значение курса заданной валюты на любую заданную календарную дату.</p>
  <p id="7RT9">Требуемый результат:</p>
  <p id="RIu1">Для валюты 1 на 03.01.2010 получить курс 32</p>
  <p id="7Mer">Для валюты 2 на 10.01.2010 получить курс 41</p>
  <p id="XS2Q">Решение через оконные функции самое универсальное, хотя в PostgreSQL можно решить через distinct on  - см <a href="https://teletype.in/@velipre_xella/vn_H211_4wx" target="_blank">заметку</a> (а в Oracle <a href="https://teletype.in/@velipre_xella/i-GZi1SZbw8" target="_blank">через keep (dense_rank ...)</a>). </p>
  <p id="GS72"><strong>Решение:</strong></p>
  <pre id="MJdo" data-lang="sql">with prep as (
select * 
,row_number() over (partition by curr_id order by date_rate desc) rn
from scott.rates
where true
and curr_id = 1
and date_rate &lt;= &#x27;2010-01-03&#x27;
)
select * from prep where rn = 1</pre>
  <p id="NMzE"><em>Также собеседующий может поинтересоваться, как такую задачу можно решить без оконных функций - на собесе в Т такое было емнип. </em></p>
  <p id="dVRg">2)Посчитайте по таблице FactSales скользящее среднее по продажам (поле SalesAmount) за окно в 3 дня (время в поле OrderDate) в разрезе StoreId, ProductId.</p>
  <p id="71mi"><strong>FactSales</strong><br /> OrderDate<br /> StoreId<br /> ProductId<br /> SalesAmount</p>
  <p id="twL0"><strong>Решение:</strong></p>
  <pre id="Zgzc" data-lang="sql">Select f.*
, avg(FactSales) over(partition by StoreId, ProductId 
order by OrderDate rows between 2 preceding and current row) running_avg
From FactSales f</pre>
  <p id="QteV">3)Дана таблица валют (справочник), необходимо написать запрос, который возвращает отсортированный список валют в алфавитном порядке по столбцу ISO_CODE, причем первыми должны идти основные валюты, с которыми работает банк: RUR, USD, EUR.</p>
  <pre id="qRkE" data-lang="sql">create table scott.currency_dict (iso_code text, iso_name text);</pre>
  <figure id="tBqJ" class="m_original">
    <img src="https://img4.teletype.in/files/f9/0e/f90e0cb6-c2e0-4899-8579-2ac7cf4f075b.png" width="307" />
    <figcaption>Исходные данные.</figcaption>
  </figure>
  <p id="XU2J"><strong>Решение:</strong></p>
  <pre id="U7gM" data-lang="sql">select
iso_code,
iso_name
from scott.currency_dict
order by 
  case iso_code when  &#x27;RUR&#x27;then 1 when &#x27;USD&#x27; then 2 when &#x27;EUR&#x27; then 3 end
  , iso_code</pre>
  <p id="RqKV">4)Необходимо получить в результате запроса только актуальные данные по каждой товарной позиции и дате начала действия ее цены<br />из этих данных построить периоды действия где дата окончания действия текущей цены является датой начала действия следующей -1 день</p>
  <pre id="qNFv" data-lang="sql">create table scott.scd2 (  
article     int,     --id товарной позиции
price       numeric,   --цена
date_from   date,    --дата начала действия цены
date_change date    --техническое поле даты изменения версии строки SCD2
)</pre>
  <figure id="MKZO" class="m_original">
    <img src="https://img1.teletype.in/files/4f/4a/4f4a21d6-072f-48b5-8e82-fb70513cc67a.png" width="380" />
    <figcaption>Исходные данные</figcaption>
  </figure>
  <figure id="UgAj" class="m_original">
    <img src="https://img4.teletype.in/files/31/7c/317c20e8-23de-468c-959a-7fb7bf8d1e38.png" width="376" />
    <figcaption>Требуемый результат</figcaption>
  </figure>
  <p id="yvsg"><em>Честно признаться, ни на собесе, ни сейчас не понял, что нужно сделать. Мутное ТЗ. Нужно уточнять, пока такой вариант решения.</em></p>
  <pre id="HbAi" data-lang="sql">with base as (
select
    article,
    price,
    date_from,
    row_number() over (partition by article, date_from order by date_change desc) as rn
from scott.scd2</pre>
  <pre id="M3Zh" data-lang="sql">)
select
    article,
    price,
    date_from,
    lead(date_from, 1, &#x27;4000-01-01&#x27;::date) over (partition by article order by date_from) as date_to
from base
where rn = 1</pre>
  <p id="A76u">5) Вариация на тему задачи 1</p>
  <figure id="NiOQ" class="m_original">
    <img src="https://img4.teletype.in/files/fd/b8/fdb8dd09-09f2-4c2d-bdcc-1ce4fedf952d.png" width="780" />
    <figcaption>Исходные данные</figcaption>
  </figure>
  <p id="cgrJ">Напишите sql запрос, который будет переводить сумму транзакций из rub в usd (ccy_code = 840) с учетом того, что в таблице rates данные только за рабочие дни. Транзакции, совершенные в выходные, пересчитываются по курсу последнего рабочего дня перед праздником/выходным. Результат: Клиент, дата, сумма операций в usd.</p>
  <p id="bQXe"><strong>Решение:</strong></p>
  <pre id="zb7Z" data-lang="sql">select client_id, t.report_date, txn_amount/r.ccy_rate amount_usd
from dbo.transactions t
left join lateral
(select * from dbo.rates r 
where r.ccy_code  = &#x27;840&#x27; 
and r.report_date &lt;= t.report_date 
order by r.report_date desc limit 1) r 
  on true</pre>
  <p id="Gwde">Это, кстати, частый обоснованный пример использования lateral join - получение TOP N значений в внешнем запросе. В том же MS SQL Server 2005 такое ещё 10+ лет назад приходилось делать, но используя кляузу outer apply вместо lateral join. В оракеле с 12 версии тоже так можно делать. </p>
  <p id="qcRW">6)</p>
  <figure id="vCaQ" class="m_original">
    <img src="https://img2.teletype.in/files/98/6f/986f133a-7f2a-4652-baac-670a7a4cd445.png" width="796" />
    <figcaption>Исходные данные</figcaption>
  </figure>
  <p id="8ORu">В таблице oper_data содержится информация по транзакциям клиентов в офисах физической сети. txn_type принимает значения debit, credit</p>
  <p id="XsCo">Напишите sql запрос, который для каждого клиента выводит сумму debit, credit операций и последний посещенный офис по месяцам. Результат представьте в виде:</p>
  <figure id="6YtO" class="m_original">
    <img src="https://img1.teletype.in/files/cd/88/cd88fccf-f2f4-46e5-896d-f8599940e911.png" width="775" />
    <figcaption>Формат требуемого результата</figcaption>
  </figure>
  <p id="xydr"><strong>Решение:</strong></p>
  <pre id="rqfT" data-lang="sql">select client_id, report_date
,sum (case when txn_type = &#x27;debit&#x27; then txn_amount else 0 end) over (partition by client_id, date_part(&#x27;month&#x27;, report_date)) debit_amount
,sum (case when txn_type = &#x27;credit&#x27; then txn_amount else 0 end) over (partition by client_id, date_part(&#x27;month&#x27;, report_date)) credit_amount
,last_value (office_number) over (partition by client_id, date_part(&#x27;month&#x27;, report_date) 
  order by report_date  rows between unbounded preceding and unbounded following) last_office
from dbo.oper_data</pre>
  <p id="sWYx">Тут вместо староверного подсчёта суммы через case (я - старовер) можно использовать кляузу filter. Ну и помнить про такие оконки, как first value/last value. Я их в проде ни разу не использовал. ¯\_(ツ)_/¯<br />Можно обойтись без last_value, если вынести расчёт последнего посещенного офиса в разрезе клиента и месяца в CTE, а потом зажойнить с расчитанными дебетовыми и кредитовыми оборотами.</p>
  <p id="alOZ"><em>Это такая не редкая задача на собесах - показать, что ты владеешь магией написать sum (case when ... Иногда даже достаточно это проговорить, что ты знаком с этой магией)).</em></p>
  <p id="E9yO">7)Задачка на знания (или воспоминания) о рекурсивных CTE - уже и такое могут на собесе навалить. Взял в канале <a href="https://t.me/data_penguin" target="_blank">https://t.me/data_penguin</a></p>
  <p id="0jDj">Для каждого сотрудника вывести его имя, уровень в иерархии (0 для топ-менеджера). Отсортировать по уровню, затем по имени.</p>
  <figure id="bwe9" class="m_original">
    <img src="https://img3.teletype.in/files/20/5b/205b52d7-cbe0-48ad-ad01-123714bd1fb3.png" width="869" />
    <figcaption>Исходные данные</figcaption>
  </figure>
  <p id="V3Y3"><strong>Решение:</strong></p>
  <pre id="z9sR" data-lang="sql">with recursive mgr_list (e_level, empno, ename, job, mgr) as (
select 0
, a.empno
, a.ename
, a.job
, a.mgr
from scott.emp a
where a.empno = 7839 --президент, у него нет начальника
union all
select e_level + 1, a.empno, a.ename, a.job, a.mgr
FROM mgr_list r 
join scott.emp a on a.mgr = r.empno
)
--вывожу больше полей, чем в ТЗ - для наглядности
select m.*
from mgr_list m
order by e_level, ename</pre>
  <p id="ce17">P.S. На недавнем собесе в Яндекс такая финальная задачка была: запрос работает год, и внезапно стал работать в 2 (может быть другое число) раза медленнее. Что бы ты предпринял?</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/mdR-HoezD3N</guid><link>https://teletype.in/@velipre_xella/mdR-HoezD3N?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/mdR-HoezD3N?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>SQL. Стоит ли &quot;орать&quot; капсом при написании sql-кода.</title><pubDate>Thu, 10 Jul 2025 16:51:36 GMT</pubDate><category>Oracle</category><description><![CDATA[Есть ещё староверы, которые капсом пишут служебные слова типа UPDATE?]]></description><content:encoded><![CDATA[
  <p id="gsRg">Есть ли ещё староверы, которые капсом пишут служебные слова типа UPDATE?!</p>
  <p id="zLJ7">Я весь сиквельный код пишу в лоу-кейсе, если иное не вменяется код-стайлом.</p>
  <p id="ciSo">Хорошую цитату нашёл сегодня, читая книжку. Она про то, что писать в аппер-кейсе - не стильно))</p>
  <blockquote id="gUuU"><em>Uppercase keywords are associated with older programming languages, such as<br />assembly, Fortran, and COBOL. SQL is an old language, which has some advantages, but there are negative connotations with our code looking ancient. Decades ago, there were good technical reasons to use upper case, but those reasons no longer apply.<br />The cultural convention today is to use lower case for programming. And lower case, or mixed case, is obviously the typical choice for normal writing. (There is a consensus that it is easier to read lowercase writing than uppercase writing. But it’s debatable why lower case is easier to read, and I’m not sure if the research applies to monospaced fonts used in programming languages.)<br />But there are certainly still times when upper case is helpful. When embedding small SQL statements inside other languages, it helps to use upper case to contrast the SQL with the other language. Upper case is also useful when writing emails or posts. And upper case can be useful for helping parts of our PL/SQL programs stick out, like for global constants.<br />Most of our time looking at code is in an IDE, where the syntax highlighting is more<br />important than using case for identifying keywords. There aren’t huge advantages to using lower case, but if it looks better, is more readable, and is easier to type, we might as well abandon upper case.</em></blockquote>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/R59yhFtKTuV</guid><link>https://teletype.in/@velipre_xella/R59yhFtKTuV?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/R59yhFtKTuV?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>Greenplum. План запроса. Рекомендации по оптимизации (draft).</title><pubDate>Sat, 19 Apr 2025 10:15:07 GMT</pubDate><category>Greenplum</category><description><![CDATA[Решил законспектировать основные вещи по сабжу из пары учебных курсов.]]></description><content:encoded><![CDATA[
  <p id="lT6F">Решил законспектировать основные вещи по сабжу из пары учебных курсов.</p>
  <p id="180f">Перед тем, как смотреть план запроса, стоит посмотреть на сам запрос. Возможно, в глаза сразу бросится код, который выглядит подозрительно неоптимальным. Например,  бывает, что остаются отладочные артефакты в коде, типа сортировки в подзапросах (order by). Или <u>ошибочный</u> union вместо union all. Или distinct по большому количеству полей. Соединения таблиц по условию неравенства или OR на ключи join. Проход таблицы более 1 раза на ровном месте (мне такие запросы аналитики регулярно подкидывают, причём разные люди, как по шаблону пишут). И тд и тп.</p>
  <p id="RwCE">На что обратить внимание в плане запроса:</p>
  <ul id="5djp">
    <li id="30Me">Узлы с наибольшей добавочной стоимостью</li>
    <li id="UDBH">Узлы с наибольшим временем выполнения (при explain analyze)</li>
    <li id="aA40">Большое количество возвращаемых строк на сегмент</li>
    <li id="CNgI">Nested Loop join при большом количестве строк</li>
    <li id="KEN9">Hash join при небольшом количестве строк</li>
    <li id="9W7k">Отсутствие Partition Selector при обращении к партиционированной таблице</li>
    <li id="1VX1">Операторы Redistribute Motion, Broadcast Motion (<em>Появление Redistribute или Broadcast Motion перед операторами соединения значит, что ключ распределения таблицы не совпадает с ключом join. Иногда это говорит о неоптимальной физической модели данных</em>). Оператор Gather Motion, появившийся в середине плана (<em>Это значит, что данные со всех сегментов обрабатываются на мастере, который значительно уступает по производительности множеству сегментов кластера.<br />В норме Gather Motion появляется только в самом конце запроса (наверху плана) для вывода результатов запроса через мастер клиенту</em>). </li>
    <li id="XnwT">Наличие факта создания спилл-файлов (<em>в выводе команды <code>EXPLAIN ANALYZE</code> один или несколько слайсов запроса отмечены звёздочкой, например * (slice1)</em>)</li>
    <li id="FOkp">rows=1 в операторах Seq Scan, Dynamic Seq Scan, Index Scan и Bitmap Heap Scan</li>
    <li id="ViBJ">Несколько Seq Scan одной таблицы (Seq Scan on my_table ... Seq Scan on my_table my_table_1 и тп) </li>
  </ul>
  <p id="IP3x">Планировщик может построить неоптимальное дерево плана запроса по разным причинам:</p>
  <ul id="YcNy">
    <li id="hs9v">Отсутствующая или неактуальная статистика, из-за которой планировщик неверно оценивает стоимость плана.</li>
    <li id="YCN5">Неоптимальная физическая модель данных, из-за которой планировщику приходится добавлять тяжёлые операторы Redistribute / Broadcast Motion и неэффективно читать данные из таблиц.</li>
    <li id="hMPr">Сам SQL-запрос, в котором логика получения результата может быть слишком сложной: планировщик не может упростить логику самостоятельно и выбирает среди заведомо неоптимальных вариантов.</li>
  </ul>
  <p id="tc45">Как можно ускорить тяжёлый запрос? Или переписать сам запрос, или оптимизация окружения выполнения запроса. Или и то, и то.</p>
  <p id="xuz1">По рефакторингу запроса это скорее магия, чем последовательность чётких инструкций. А по второму варианту - это актуализация статистики, изменение физической модели и, возможно, изменение типа планировщика.</p>
  <p id="d9pB"><em>В Гринплуне существует 2 оптимизатора - легаси и GPORCA. Предания гласят, что в большинстве случаев GPORCA справляется лучше. Поэтому стоит знать, что если в коде используются чисто постгресовские кляузы типа distinct on, то будет использоваться не GPORCA, а легаси оптимизатор.</em></p>
  <p id="UizV">Изменение физической модели это, например,  разбиение сложного запроса с большим количеством джойнов на несколько, с последующей материализацией промежуточных результатов в таблицы (и созданием подходящих ключей дистрибуции в них). И в финале собрать исходный запрос уже с этими таблицами.</p>
  <p id="oKBT">P.S. Автор ТГ-канала Инженерообязанный на ютубе выложил вполне годное видео по сабжу <a href="https://www.youtube.com/watch?v=jdYcl-86Uxo&ab_channel=%D0%98%D0%BD%D0%B6%D0%B5%D0%BD%D0%B5%D1%80%D0%BE%D0%BE%D0%B1%D1%8F%D0%B7%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%F0%9F%AB%A1" target="_blank">Простая оптимизация запросов в GreenPlum + кейсы</a>  </p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@velipre_xella/zR-7KTYHiw3</guid><link>https://teletype.in/@velipre_xella/zR-7KTYHiw3?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella</link><comments>https://teletype.in/@velipre_xella/zR-7KTYHiw3?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=velipre_xella#comments</comments><dc:creator>velipre_xella</dc:creator><title>dbt. Pre-hooks and Post-hooks (draft)</title><pubDate>Thu, 17 Apr 2025 18:13:52 GMT</pubDate><category>DBT</category><description><![CDATA[<img src="https://img4.teletype.in/files/b6/49/b6491e05-6328-4e7e-87e7-5e9ad2c53f0b.png"></img>Pre-hook - это 1 или более sql-выражений, выполняемых до построения ресурса типа модели (или seed, snapshot). Post-hook - то же самое, но выполняемое, соответственно, после построения. Также в хуках могу вызываться макросы, которые выполняют sql-выражения.]]></description><content:encoded><![CDATA[
  <p id="8EWQ">Pre-hook - это 1 или более sql-выражений, выполняемых до построения ресурса типа модели (или seed, snapshot). Post-hook - то же самое, но выполняемое, соответственно, после построения. Также в хуках могу вызываться макросы, которые выполняют sql-выражения.</p>
  <p id="4Td0">Если в хуке выполняется единственный запрос, текст запроса оформляется в кавычки (см пример такого конфига модели ниже)</p>
  <pre id="Aie2" data-lang="sql">{{ config
(materialized=&#x27;table&#x27;,
alias=&#x27;emp&#x27;,
schema=&#x27;ods_scott&#x27;,
tags=[&#x27;ods_layer&#x27;],
pre_hook=&quot;
insert into scott.model_run_log (log_text, log_dt) 
values (&#x27;{{this.schema}}.{{this.table}} start&#x27; , now())
&quot;
)
}}</pre>
  <p id="1Omq">В случае, если в хуке несколько sql-выражений или происходит вызов макроса, всё это оборачивается в квадратные скобки.</p>
  <p id="KqD2">Пример вызова нескольких команд DDL в прехуке:</p>
  <pre id="BtBV" data-lang="sql">pre_hook=[&quot;truncate table t1;&quot;, &quot;truncate table t2;&quot;,&quot;truncate table t3;&quot;]</pre>
  <p id="RGUo">Пример вызова макроса в прехуке:</p>
  <pre id="OIgX" data-lang="sql">pre_hook = [&quot;{{truncate_table()}}&quot;]</pre>
  <p id="B51a">Для реляционных СУБД хуки выполняются в той же транзакции, что и выполнение базового ресурса (модели и тд).</p>
  <p id="gUM9">Чтобы изменить такое поведение и реализовать что-то навроде автономных транзакций, можно использовать в блоке конфигурации ресурса вспомогательные макросы before_begin и after_commit.</p>
  <p id="Thbw">На рисунке ниже модель не будет собрана, но sql-statement из прехука (запись в таблицу аудита) выполнен будет.</p>
  <figure id="b7uf" class="m_original">
    <img src="https://img4.teletype.in/files/b6/49/b6491e05-6328-4e7e-87e7-5e9ad2c53f0b.png" width="659" />
    <figcaption>Пример конфигурации с макросами before_begin и after_commit</figcaption>
  </figure>
  <p id="Gv1d">IRL в прехуке видел только truncate таблицы в инкрементальных моделях. В постхуках мне пока ничего кроме команды analyze не встречалось.</p>
  <p id="0xT6">P.S. Напоминаю, что одиночный sql-statement, не возвращающий dataset, также можно выполнить с использованием функции run_query(), например:</p>
  <pre id="4aYW" data-lang="python">{%do run_query (&#x27;truncate table scott.model_run_log&#x27;)%}</pre>

]]></content:encoded></item></channel></rss>