<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>\&quot; [] _ () &quot;/</title><subtitle>Всякое об SQL и ETL.</subtitle><author><name>\&quot; [] _ () &quot;/</name></author><id>https://teletype.in/atom/velipre_xella</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/velipre_xella?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@velipre_xella?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=velipre_xella"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/velipre_xella?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-07T09:07:48.691Z</updated><entry><id>velipre_xella:83SVMuKRrUB</id><link rel="alternate" type="text/html" href="https://teletype.in/@velipre_xella/83SVMuKRrUB?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=velipre_xella"></link><title>PostgreSQL. Использование USING и RETURNING в команде DELETE.</title><published>2026-01-17T08:32:44.404Z</published><updated>2026-01-26T08:31:08.068Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/8c/5c/8c5ca736-29c1-4bfd-8b0f-22879ad9f11e.png"></media:thumbnail><category term="postgresql" label="Postgresql"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/69/a9/69a96456-0cb7-4c07-af6d-50f2cd414149.png&quot;&gt;В заметке рассказывается использование предложений USING и RETURNING в команде DELETE.</summary><content type="html">
  &lt;p id=&quot;hZd9&quot;&gt;В заметке рассказывается использование предложений USING и RETURNING в команде DELETE.&lt;/p&gt;
  &lt;p id=&quot;0Bgj&quot;&gt;Воспользуемся известной схемой SCOTT. Допустим, мы хотим удалить из таблицы emp всех работников из департамента с названием SALES.&lt;/p&gt;
  &lt;figure id=&quot;p8Y5&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/69/a9/69a96456-0cb7-4c07-af6d-50f2cd414149.png&quot; width=&quot;327&quot; /&gt;
    &lt;figcaption&gt;Таблица департаментов dept&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;nb8k&quot;&gt;По стандарту эту задачу можно решить так:&lt;/p&gt;
  &lt;pre id=&quot;ZbOO&quot; data-lang=&quot;sql&quot;&gt;delete
from scott.emp e
where e.deptno in (select deptno from scott.dept d where d.dname = &amp;#x27;SALES&amp;#x27;);&lt;/pre&gt;
  &lt;p id=&quot;IwKf&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;grgr&quot;&gt;&lt;code&gt;&lt;strong&gt;Но PostgreSQL при удалении позволяет ссылаться на столбцы других таблиц в условии WHERE, когда эти таблицы перечисляются в предложении USING. Таким образом решение указанной задачи с использованием предложения USING выглядит так: &lt;/strong&gt;&lt;/code&gt;&lt;/p&gt;
  &lt;pre id=&quot;RGlY&quot; data-lang=&quot;sql&quot;&gt;delete
from scott.emp e
using scott.dept d
where e.deptno = d.deptno and d.dname = &amp;#x27;SALES&amp;#x27;;&lt;/pre&gt;
  &lt;p id=&quot;44F7&quot;&gt;По сути, это дилит с джойном. В MS SQL Server можно было бы в лоб написать join (код ниже), а PostgreSQL пошёл своим путём.&lt;/p&gt;
  &lt;pre id=&quot;aD74&quot; data-lang=&quot;sql&quot;&gt;-- вариант удаления в ms sql server через join 
-- (но это не точно, пишу по памяти)
delete e
from scott.emp e
join scott.dept d
  on e.deptno = d.deptno and d.dname = &amp;#x27;SALES&amp;#x27;;&lt;/pre&gt;
  &lt;p id=&quot;P021&quot;&gt;В документации пишется, что &lt;em&gt;&amp;quot;В ряде случаев запрос в стиле соединения легче написать и он может работать быстрее, чем в стиле вложенного запроса&amp;quot;. &lt;/em&gt;Так это или нет можно убедиться только на практике.&lt;/p&gt;
  &lt;p id=&quot;66hK&quot;&gt;Кстати, чтобы посмотреть реальный план команды DELETE без фактического её выполнения, можно начать транзакцию, выполнить explain analyze запроса, и потом откатить выполнение командой rollback (рисунок ниже).&lt;/p&gt;
  &lt;figure id=&quot;ccUU&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7f/34/7f348053-cc2f-43f6-b6f9-1ed0f4707f25.png&quot; width=&quot;637&quot; /&gt;
    &lt;figcaption&gt;Просмотр реального плана выполнения команды с последующим откатом.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;9xl9&quot;&gt;Если бы вдруг перед удалением захотелось посмотреть, какие записи из таблицы удаляются, то  помогла бы кляуза RETURNING.&lt;/p&gt;
  &lt;figure id=&quot;ftX5&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ad/7f/ad7ff6f4-d489-4a9c-acb9-304148dff22a.png&quot; width=&quot;839&quot; /&gt;
    &lt;figcaption&gt;Вывод удаляемых записей&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;P9RI&quot;&gt;&lt;code&gt;Также RETURNING может использоваться в командах INSERT, UPDATE и MERGE для получения данных из модифицируемых строк. Подробно об этом можно почитать в &lt;a href=&quot;https://postgrespro.ru/docs/postgresql/current/dml-returning&quot; target=&quot;_blank&quot;&gt;документации&lt;/a&gt;. &lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;Y0Br&quot;&gt;P.S. А ещё кляуза USING может использоваться в ORDER BY. Если верить документации.&lt;/p&gt;
  &lt;figure id=&quot;2ecT&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/76/f1/76f16160-6c7f-4ada-b62a-3a6d70f2397c.png&quot; width=&quot;1108&quot; /&gt;
    &lt;figcaption&gt;Синтаксис команды SELECT в доке&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;H7Kp&quot;&gt;Но почему-то в статье документации &lt;a href=&quot;https://postgrespro.ru/docs/postgresql/current/queries-order&quot; target=&quot;_blank&quot;&gt;7.5. Сортировка строк (&lt;code&gt;ORDER BY&lt;/code&gt;)&lt;/a&gt; USING уже отсутствует. Может, плохо искал (&lt;/p&gt;
  &lt;figure id=&quot;6Inl&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9a/a6/9aa63b73-8f85-4ca7-9ff4-546bc7d43d23.png&quot; width=&quot;1349&quot; /&gt;
  &lt;/figure&gt;

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

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

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

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

</content></entry><entry><id>velipre_xella:mdR-HoezD3N</id><link rel="alternate" type="text/html" href="https://teletype.in/@velipre_xella/mdR-HoezD3N?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=velipre_xella"></link><title>SQL. Стоит ли &quot;орать&quot; капсом при написании sql-кода.</title><published>2025-07-10T16:51:36.814Z</published><updated>2025-10-11T12:19:46.257Z</updated><category term="oracle" label="Oracle"></category><summary type="html">Есть ещё староверы, которые капсом пишут служебные слова типа UPDATE?</summary><content type="html">
  &lt;p id=&quot;gsRg&quot;&gt;Есть ли ещё староверы, которые капсом пишут служебные слова типа UPDATE?!&lt;/p&gt;
  &lt;p id=&quot;zLJ7&quot;&gt;Я весь сиквельный код пишу в лоу-кейсе, если иное не вменяется код-стайлом.&lt;/p&gt;
  &lt;p id=&quot;ciSo&quot;&gt;Хорошую цитату нашёл сегодня, читая книжку. Она про то, что писать в аппер-кейсе - не стильно))&lt;/p&gt;
  &lt;blockquote id=&quot;gUuU&quot;&gt;&lt;em&gt;Uppercase keywords are associated with older programming languages, such as&lt;br /&gt;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.&lt;br /&gt;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.)&lt;br /&gt;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.&lt;br /&gt;Most of our time looking at code is in an IDE, where the syntax highlighting is more&lt;br /&gt;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.&lt;/em&gt;&lt;/blockquote&gt;

</content></entry><entry><id>velipre_xella:R59yhFtKTuV</id><link rel="alternate" type="text/html" href="https://teletype.in/@velipre_xella/R59yhFtKTuV?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=velipre_xella"></link><title>Greenplum. План запроса. Рекомендации по оптимизации (draft).</title><published>2025-04-19T10:15:07.285Z</published><updated>2026-04-03T10:28:44.211Z</updated><category term="greenplum" label="Greenplum"></category><summary type="html">Решил законспектировать основные вещи по сабжу из пары учебных курсов.</summary><content type="html">
  &lt;p id=&quot;lT6F&quot;&gt;Решил законспектировать основные вещи по сабжу из пары учебных курсов.&lt;/p&gt;
  &lt;p id=&quot;180f&quot;&gt;Перед тем, как смотреть план запроса, стоит посмотреть на сам запрос. Возможно, в глаза сразу бросится код, который выглядит подозрительно неоптимальным. Например,  бывает, что остаются отладочные артефакты в коде, типа сортировки в подзапросах (order by). Или &lt;u&gt;ошибочный&lt;/u&gt; union вместо union all. Или distinct по большому количеству полей. Соединения таблиц по условию неравенства или OR на ключи join. Проход таблицы более 1 раза на ровном месте (мне такие запросы аналитики регулярно подкидывают, причём разные люди, как по шаблону пишут). И тд и тп.&lt;/p&gt;
  &lt;p id=&quot;RwCE&quot;&gt;На что обратить внимание в плане запроса:&lt;/p&gt;
  &lt;ul id=&quot;5djp&quot;&gt;
    &lt;li id=&quot;30Me&quot;&gt;Узлы с наибольшей добавочной стоимостью&lt;/li&gt;
    &lt;li id=&quot;UDBH&quot;&gt;Узлы с наибольшим временем выполнения (при explain analyze)&lt;/li&gt;
    &lt;li id=&quot;aA40&quot;&gt;Большое количество возвращаемых строк на сегмент&lt;/li&gt;
    &lt;li id=&quot;CNgI&quot;&gt;Nested Loop join при большом количестве строк&lt;/li&gt;
    &lt;li id=&quot;KEN9&quot;&gt;Hash join при небольшом количестве строк&lt;/li&gt;
    &lt;li id=&quot;9W7k&quot;&gt;Отсутствие Partition Selector при обращении к партиционированной таблице&lt;/li&gt;
    &lt;li id=&quot;1VX1&quot;&gt;Операторы Redistribute Motion, Broadcast Motion (&lt;em&gt;Появление Redistribute или Broadcast Motion перед операторами соединения значит, что ключ распределения таблицы не совпадает с ключом join. Иногда это говорит о неоптимальной физической модели данных&lt;/em&gt;). Оператор Gather Motion, появившийся в середине плана (&lt;em&gt;Это значит, что данные со всех сегментов обрабатываются на мастере, который значительно уступает по производительности множеству сегментов кластера.&lt;br /&gt;В норме Gather Motion появляется только в самом конце запроса (наверху плана) для вывода результатов запроса через мастер клиенту&lt;/em&gt;). &lt;/li&gt;
    &lt;li id=&quot;XnwT&quot;&gt;Наличие факта создания спилл-файлов (&lt;em&gt;в выводе команды &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; один или несколько слайсов запроса отмечены звёздочкой, например * (slice1)&lt;/em&gt;)&lt;/li&gt;
    &lt;li id=&quot;FOkp&quot;&gt;rows=1 в операторах Seq Scan, Dynamic Seq Scan, Index Scan и Bitmap Heap Scan&lt;/li&gt;
    &lt;li id=&quot;ViBJ&quot;&gt;Несколько Seq Scan одной таблицы (Seq Scan on my_table ... Seq Scan on my_table my_table_1 и тп) &lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;IP3x&quot;&gt;Планировщик может построить неоптимальное дерево плана запроса по разным причинам:&lt;/p&gt;
  &lt;ul id=&quot;YcNy&quot;&gt;
    &lt;li id=&quot;hs9v&quot;&gt;Отсутствующая или неактуальная статистика, из-за которой планировщик неверно оценивает стоимость плана.&lt;/li&gt;
    &lt;li id=&quot;YCN5&quot;&gt;Неоптимальная физическая модель данных, из-за которой планировщику приходится добавлять тяжёлые операторы Redistribute / Broadcast Motion и неэффективно читать данные из таблиц.&lt;/li&gt;
    &lt;li id=&quot;hMPr&quot;&gt;Сам SQL-запрос, в котором логика получения результата может быть слишком сложной: планировщик не может упростить логику самостоятельно и выбирает среди заведомо неоптимальных вариантов.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;tc45&quot;&gt;Как можно ускорить тяжёлый запрос? Или переписать сам запрос, или оптимизация окружения выполнения запроса. Или и то, и то.&lt;/p&gt;
  &lt;p id=&quot;xuz1&quot;&gt;По рефакторингу запроса это скорее магия, чем последовательность чётких инструкций. А по второму варианту - это актуализация статистики, изменение физической модели и, возможно, изменение типа планировщика.&lt;/p&gt;
  &lt;p id=&quot;d9pB&quot;&gt;&lt;em&gt;В Гринплуне существует 2 оптимизатора - легаси и GPORCA. Предания гласят, что в большинстве случаев GPORCA справляется лучше. Поэтому стоит знать, что если в коде используются чисто постгресовские кляузы типа distinct on, то будет использоваться не GPORCA, а легаси оптимизатор.&lt;/em&gt;&lt;/p&gt;
  &lt;p id=&quot;UizV&quot;&gt;Изменение физической модели это, например,  разбиение сложного запроса с большим количеством джойнов на несколько, с последующей материализацией промежуточных результатов в таблицы (и созданием подходящих ключей дистрибуции в них). И в финале собрать исходный запрос уже с этими таблицами.&lt;/p&gt;
  &lt;p id=&quot;oKBT&quot;&gt;P.S. Автор ТГ-канала Инженерообязанный на ютубе выложил вполне годное видео по сабжу &lt;a href=&quot;https://www.youtube.com/watch?v=jdYcl-86Uxo&amp;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&quot; target=&quot;_blank&quot;&gt;Простая оптимизация запросов в GreenPlum + кейсы&lt;/a&gt;  &lt;/p&gt;

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

</content></entry><entry><id>velipre_xella:aSuvoiV0BeD</id><link rel="alternate" type="text/html" href="https://teletype.in/@velipre_xella/aSuvoiV0BeD?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=velipre_xella"></link><title>Разные полезности в dbt (draft)</title><published>2025-04-16T14:57:13.657Z</published><updated>2025-04-20T16:45:00.782Z</updated><category term="dbt" label="DBT"></category><summary type="html">Решил собрать разные полезные при работе с dbt вещи в одной заметке.</summary><content type="html">
  &lt;p id=&quot;7mKx&quot;&gt;Решил собрать разные полезные при работе с dbt вещи в одной заметке. Все примеры для работы с PostgreSQL.&lt;/p&gt;
  &lt;h2 id=&quot;oJeu&quot;&gt;Использование функций ref и sources.&lt;/h2&gt;
  &lt;p id=&quot;3Ki1&quot;&gt;Не нужно использовать прямые ссылки на реальные таблицы БД, как вы это делаете в обычных запросах SQL. Вместо этого используйте функцию ref - для ссылки на существующую модель, и функцию sources.&lt;/p&gt;
  &lt;p id=&quot;GHWU&quot;&gt;Даже если проект никогда не будет запускаться целиком и проблема неверного порядка сбора моделей отсутствует, теряется фича dbt в части сбора зависимостей. И, соответственно, у вас нет lineage из коробки и генерации верной документации.&lt;/p&gt;
  &lt;p id=&quot;lsXe&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;mGS8&quot;&gt;Конфигурации&lt;/h2&gt;
  &lt;p id=&quot;eH7l&quot;&gt;Пояснять буду на блоке конфигурации для модели ods_emp.sql. Весь код модели ниже.&lt;/p&gt;
  &lt;pre id=&quot;GBLY&quot; data-lang=&quot;sql&quot;&gt;{{ config
(materialized=&amp;#x27;table&amp;#x27;,
alias=&amp;#x27;emp&amp;#x27;,
schema=&amp;#x27;ods_scott&amp;#x27;,
tags=[&amp;#x27;ods_layer&amp;#x27;],
enabled = true
)
}}&lt;/pre&gt;
  &lt;pre id=&quot;ID9N&quot; data-lang=&quot;python&quot;&gt;select * 
from scott.dept
{% if var(&amp;#x27;use_limit&amp;#x27;) == 1 %}
limit 1
{% endif %}&lt;/pre&gt;
  &lt;p id=&quot;VgqV&quot;&gt;Явно указав значение alias и  schema, при выполнении модели получим таблицу с именем emp (а не ods_emp, как название модели) в кастомной схеме ods_scott.&lt;/p&gt;
  &lt;p id=&quot;LyYu&quot;&gt;Указав тег ods_layer, можно запустить выполнение всех моделей с этим тегом таким образом:&lt;/p&gt;
  &lt;pre id=&quot;Fmo3&quot; data-lang=&quot;shell&quot;&gt;dbt run --select tag:ods_layer&lt;/pre&gt;
  &lt;figure id=&quot;1MnQ&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f9/58/f958a59f-ef2b-4c43-8efa-7409550f3765.png&quot; width=&quot;528&quot; /&gt;
    &lt;figcaption&gt;Запуск всех моделей с выбранным тегом&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ocNs&quot;&gt;Кстати, если бы все модели с этим тегом лежали бы в папке models/ods - как у меня в учебном проекте - этот же результат получался командой &lt;/p&gt;
  &lt;pre id=&quot;LUcS&quot; data-lang=&quot;shell&quot;&gt;dbt run -m ods&lt;/pre&gt;
  &lt;p id=&quot;l1XJ&quot;&gt;При необходимости можно запустить все модели с указанным тегом, исключая указанные, используя флаг exclude&lt;/p&gt;
  &lt;pre id=&quot;ryCV&quot; data-lang=&quot;shell&quot;&gt;dbt run --select tag:ods_layer --exclude ods_emp&lt;/pre&gt;
  &lt;p id=&quot;JyQd&quot;&gt;Если бы PostgreSQL поддерживал обращение к разным базам на одном инстансе, помимо параметра конфигурации &lt;em&gt;&lt;strong&gt;schema&lt;/strong&gt;&lt;/em&gt; можно было бы указать и в параметре &lt;strong&gt;&lt;em&gt;database&lt;/em&gt;&lt;/strong&gt; название БД, отличной от прописанной в файле profiles.yml. Но в постгресе так не получится, выбросится ошибка типа ERROR: Cross-db references not allowed in postgres.&lt;/p&gt;
  &lt;p id=&quot;cFwY&quot;&gt;Для &lt;em&gt;&lt;strong&gt;enabled&lt;/strong&gt;&lt;/em&gt; значение true по умолчанию, т.е. стоит использовать со значением false, если указанный ресурс по каким-то причинам нужно отключить, чтобы он не участвовал в проекте. Если нужно исключить модель из конкретного запуска, стоит использовать флаг &lt;em&gt;exclude&lt;/em&gt; - смотри пример выше. &lt;/p&gt;
  &lt;h2 id=&quot;k4nX&quot;&gt;Запуск модели с переменными&lt;/h2&gt;
  &lt;p id=&quot;p24h&quot;&gt;Можно передать значение 1 переменной &amp;#x27;use_limit&amp;#x27;  нашей модели ods_emp при запуске из CLI таким образом:&lt;/p&gt;
  &lt;pre id=&quot;6RH1&quot; data-lang=&quot;shell&quot;&gt;dbt run -m ods_emp --vars &amp;quot;{&amp;#x27;use_limit&amp;#x27;: 1}&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;mZKz&quot;&gt;В проде, скорее всего, значения переменных по умолчанию будут прописаны в файле проекта dbt_project.yml, а по запросу они будут передаваться из стороннего приложения, например, Airflow.&lt;/p&gt;
  &lt;h2 id=&quot;OuMO&quot;&gt;Analyses&lt;/h2&gt;
  &lt;p id=&quot;x9Eb&quot;&gt;Это отдельная сущность в проекте dbt, по сути представляет собой запрос, сохраненный не где-то в папке у аналитика или разработчика, а в самом проекте. Плюсы в возможности использования jinja и макросов для сложносочиненной логики. И хранение под системой контроля версий.&lt;/p&gt;
  &lt;p id=&quot;Rika&quot;&gt;При выполнении никакой объект в БД не материализуется, получаем готовый запрос ресурса analyses мы при компиляции, найти его можно в папке \target\compiled\dbt_edu_proj\analyses\&lt;/p&gt;
  &lt;figure id=&quot;ohkh&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0b/01/0b018e62-3c8f-4b82-a7b0-57b4b97db391.png&quot; width=&quot;1459&quot; /&gt;
    &lt;figcaption&gt;Analysis в проекте&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4O9k&quot;&gt;Для подобных целей - хранений запросов в проекте без их материализации в БД - могут служить модели с материализацией типа &lt;strong&gt;ephemeral&lt;/strong&gt;. Но в отличие от Analyses такие модели (для dbt это CTE) могут использоваться в сборке других. Что, скорее всего, упростит чтение и усложнит  понимание кода.&lt;/p&gt;
  &lt;h2 id=&quot;dOve&quot;&gt;&lt;strong&gt;Расширение dbt Power User для VS Code&lt;/strong&gt;&lt;/h2&gt;
  &lt;p id=&quot;nn0y&quot;&gt;Очень полезная вещь, кучу времени экономит.&lt;/p&gt;
  &lt;h3 id=&quot;BGJ4&quot;&gt;Execute dbt SQL&lt;/h3&gt;
  &lt;p id=&quot;DYcm&quot;&gt;Запускается из блока пиктограмм в правом верхнем углу IDE&lt;/p&gt;
  &lt;figure id=&quot;CRpz&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f0/54/f054245b-79b2-463f-9e80-d55d1f1aedde.png&quot; width=&quot;408&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;6vzA&quot;&gt;Можно запускать отдельно селекты моделей. Посмотришь данные, а при инвалидном запросе получишь ошибку.&lt;/p&gt;
  &lt;figure id=&quot;lIXZ&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e5/52/e552d314-38e5-4125-8464-da0205f1b2f2.png&quot; width=&quot;720&quot; /&gt;
    &lt;figcaption&gt;Пример неуспешного запуска&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;2xG4&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/27/8d/278dff89-16b9-44a7-948c-64bb62265805.png&quot; width=&quot;781&quot; /&gt;
    &lt;figcaption&gt;Пример успешного запуска&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;XwOU&quot;&gt;Кстати, в терминале посмотреть вывод результатов - или сам факт, что результаты возвращаются, можно командой dbt show (ни же 2 варианта использования)&lt;/p&gt;
  &lt;pre id=&quot;IBFy&quot; data-lang=&quot;shell&quot;&gt;dbt show -m dds_emp_ext
dbt show --inline &amp;quot;select * from {{ ref(&amp;#x27;dds_emp_ext&amp;#x27;) }}&amp;quot;
&lt;/pre&gt;
  &lt;figure id=&quot;BMUt&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d7/31/d7312d6e-4b34-42a7-bed9-c06af432ec28.png&quot; width=&quot;596&quot; /&gt;
    &lt;figcaption&gt;Применение команды dbt show&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;oGmX&quot;&gt;Lineage&lt;/h3&gt;
  &lt;p id=&quot;U6xr&quot;&gt;Можно смотреть lineage сразу в IDE, без использования других инструментов. Находится через верхнее меню Terminal=&amp;gt; New Terminal.&lt;/p&gt;
  &lt;p id=&quot;Vok7&quot;&gt;Для примера на рисунке ниже это не очень важная опция, но если моделей уже порядка десятка, это сильно облегчает жизнь.&lt;/p&gt;
  &lt;figure id=&quot;yOMA&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1e/13/1e13aab3-8355-4ff5-9196-2d0ad74f98df.png&quot; width=&quot;1048&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ZNmh&quot;&gt;Очень полезная пиктограмма для показа скомилированного кода в соседнем окне IDE &lt;strong&gt;Compiled dbt Preview&lt;/strong&gt;:&lt;/p&gt;
  &lt;figure id=&quot;Ianz&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c6/e1/c6e1587e-978f-4469-9f91-41f01ec0b80f.png&quot; width=&quot;275&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;CsdK&quot;&gt;Есть пиктограммы, которые просто дублируют команды терминала dbt run, dbt test, dbt compile.&lt;/p&gt;

</content></entry><entry><id>velipre_xella:5vYvijJX-1g</id><link rel="alternate" type="text/html" href="https://teletype.in/@velipre_xella/5vYvijJX-1g?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=velipre_xella"></link><title>Создание виртуальной среды в Windows и использование её в PyCharm и VS Code.</title><published>2025-03-11T16:48:46.350Z</published><updated>2025-11-30T14:41:26.336Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/d4/92/d4921a04-a998-4a31-89a2-b2812ee933f1.png"></media:thumbnail><category term="python" label="Python"></category><summary type="html">В заметке кратко рассказывается о создании вручную виртуальной среды и дальнейшем использовании её в PyCharm и VS Code.</summary><content type="html">
  &lt;p id=&quot;iB7r&quot;&gt;В заметке кратко рассказывается о создании вручную виртуальной среды и дальнейшем использовании её в PyCharm и VS Code.&lt;/p&gt;
  &lt;p id=&quot;GvuL&quot;&gt;Для создания виртуальной среды я заранее создал папку c:\IT_Learning\py_venv\. В терминале командой cd делаем эту папку текущей &lt;/p&gt;
  &lt;pre id=&quot;5y0k&quot; data-lang=&quot;shell&quot;&gt;cd c:\IT_Learning\py_venv\&lt;/pre&gt;
  &lt;p id=&quot;9vtq&quot;&gt;Создаем следующей командой виртуальную среду&lt;/p&gt;
  &lt;pre id=&quot;cgDx&quot; data-lang=&quot;shell&quot;&gt;python -m venv venv &lt;/pre&gt;
  &lt;p id=&quot;VUqg&quot;&gt;Активируем её&lt;/p&gt;
  &lt;pre id=&quot;P9A2&quot; data-lang=&quot;shell&quot;&gt;.\venv\Scripts\activate&lt;/pre&gt;
  &lt;p id=&quot;ifDk&quot;&gt;В терминале (см рисунок ниже) в последней строке отображается имя активной виртуальной среды&lt;/p&gt;
  &lt;figure id=&quot;5xkz&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/55/25/55256f57-6955-469d-9e0e-80b1f12abd53.png&quot; width=&quot;609&quot; /&gt;
    &lt;figcaption&gt;На картинке папка с виртуальной средой называется py_venv_new, т.к. py_venv мной была использована ранее. Для целей заметки это неважно.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;dK77&quot;&gt;Теперь открываем PyCharm, создаём новый проект и в появившемся окне выбираем следующие настройки (на рисунке ниже указаны стрелками)&lt;/p&gt;
  &lt;figure id=&quot;Qx6p&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a5/45/a545241e-1794-41ad-aeec-c5ad8f23b978.jpeg&quot; width=&quot;1005&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VPU8&quot;&gt;Как видите, необязательно, чтобы виртуальная среда находилась там же, где и файлы самого проекта.&lt;/p&gt;
  &lt;p id=&quot;eqRE&quot;&gt;Ура, теперь если у меня опять возникнет перерыв на несколько месяцев с работой в PyCharm, я уже не буду гуглить, как там настраивается venv.&lt;/p&gt;
  &lt;h2 id=&quot;yr35&quot;&gt;VS Code&lt;/h2&gt;
  &lt;p id=&quot;Fv8Y&quot;&gt;В IDE нажимаем одновременно Ctrl + Shift + P, чтобы открыть Command Palette.&lt;/p&gt;
  &lt;p id=&quot;aCrp&quot;&gt;Набираем в ней Python: Select Interpreter.&lt;/p&gt;
  &lt;p id=&quot;LxyC&quot;&gt;Жамкаем Enter interpreter path&lt;/p&gt;
  &lt;figure id=&quot;VnA7&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/68/39/683903e6-75eb-46d1-82c6-661621a82b76.png&quot; width=&quot;656&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VHEt&quot;&gt;Выбираем нужный python.exe&lt;/p&gt;
  &lt;figure id=&quot;I4lj&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1f/f6/1ff62b39-183a-4904-86ac-7e5f021ae69e.png&quot; width=&quot;683&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;eP7C&quot;&gt;В панели активности в левом нижнем углу IDE видим выбранную версию. Ура!&lt;/p&gt;
  &lt;figure id=&quot;FMtk&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/85/40/85407d44-06a2-40ef-9d88-eb32c982ee69.png&quot; width=&quot;278&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;G0dd&quot;&gt;P.S. Понадобилось использовать более свежую версию python, чем та, что установлена у меня основной. Установил 3.11 и далее создал виртуальную среду следующей командой.&lt;/p&gt;
  &lt;pre id=&quot;Ya08&quot; data-lang=&quot;shell&quot;&gt;py -3.11 -m venv venv&lt;/pre&gt;
  &lt;p id=&quot;Ya08&quot;&gt;Этого же можно было достичь, используя вместо venv, идущей в комплекте, virtualenv&lt;/p&gt;
  &lt;pre id=&quot;hO9e&quot; data-lang=&quot;shell&quot;&gt;pip install virtualenv
virtualenv venv -p &amp;#x27;c:\Progs\Python311\python.exe&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;uWZr&quot;&gt;P.P.S. Посмотреть установленные пакеты можно командой pip list, сторонние пакеты - pip freeze.&lt;/p&gt;
  &lt;p id=&quot;P1RT&quot;&gt;Посмотреть все установленные версии питона можно командой Powershell &lt;/p&gt;
  &lt;pre id=&quot;0Q5O&quot; data-lang=&quot;powershell&quot;&gt;py -0p
&lt;/pre&gt;
  &lt;figure id=&quot;Y73Y&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/73/21/73211ff2-e026-4cc4-a30a-b4fd9d7c84a3.png&quot; width=&quot;516&quot; /&gt;
  &lt;/figure&gt;

</content></entry></feed>