← Все курсы · Курс SQL · Все руководства

SQL CTE: как построить запрос по шагам, которые вы можете проверить

Называйте каждый шаг и проверяйте его перед запуском следующего · Написано для прослушивания · Часть Analyst Prep Kit

К , аналитик данных · Опубликовано

К концу этой страницы вы можете взять вопрос, требующий четырех решений, и записать его в виде четырех именованных шагов, каждый из которых можно проверить отдельно. Вы будете знать WITH синтаксис, как совместить один шаг с другим, как проверить количество строк шага перед тем, как его использовать, и когда CTE — неправильный инструмент. Это около двадцати пяти минут.

Вот что на самом деле с этим делать. В следующий раз, когда запрос станет настолько длинным, что вам придется прокручивать его, чтобы понять, остановитесь и дайте имя его первому шагу. Затем запустите этот шаг и посмотрите на количество строк. Почти каждый запрос, выдающий неправильное число, был бы тут же перехвачен.

Краткая версия: CTE — это именованный временный результат, который вы определяете перед основным запросом. Это превращает один запрос, который вы не можете проверить, в несколько, которые вы можете проверить.

Здесь одна идея решает все остальное, поэтому она и дает представление о картине. Каждый именованный шаг берет строки предыдущего шага и сужает их, а каждая стрелка имеет счетчик строк, который вы можете прочитать.

A query as four named steps Four boxes arranged left to right, each connected to the next by an arrow, and each box smaller in height than the one before it. The first box is labelled all games and holds 82,956. An arrow leads to the second box, labelled rated, holding 10,507. An arrow leads to the third box, labelled well liked, holding 3,454. An arrow leads to the fourth box, labelled overlooked, holding 663. A final arrow leads to a result marker holding 175. Above each arrow is a small tick mark, showing that the row count can be read and checked at every step rather than only at the end. The shrinking boxes show a funnel in which every stage is visible and countable. 82,956 all games 10,507 rated 3,454 well liked 663 overlooked 175 Every step has a name and a number you can read on its own. A nested query has neither until the end.
В любом случае одна и та же работа. Только одна версия позволяет проверить ее наполовину.
Что вы узнаете
  1. Что такое CTE, в одном запросе
  2. Сопоставление шагов и проверка каждого из них
  3. Реальный пример четырех шагов
  4. CTE против подзапроса, тот же ответ
  5. Полное до и после
  6. Пограничные случаи и честные ограничения
  7. Почему это работает
  8. Использование этого в вашем собственном проекте
  9. Все на одном экране
Проработанный пример реальный. Все цифры на этой странице взяты из опубликованного портфолио проекта: найти действительно игнорируемые игры в каталоге из 82 956 игр по адресу Скрытые сокровища Пар на GitHub. Приведенные ниже шаги представляют собой фактическое определение, из которого был составлен список из 175 игр. Если GROUP BY это новинка, читайте GROUP BY и HAVING во-первых, потому что от этого зависит третий шаг.

1. Что такое CTE в одном запросе

Перед объяснением: вы пишите WITH rated AS (...) в верхней части запроса. Является rated реальная таблица, которая сейчас существует в вашей базе данных, и сможет ли коллега запросить ее завтра?

CTE означает общее табличное выражение. Не обращайте внимания на имя. Это способ сказать: «Сначала разберись и назови это X», а затем использовать X в запросе ниже, как если бы это была таблица.

WITH rated AS (
    SELECT *
    FROM games
    WHERE TotalReviews >= 500
)
SELECT COUNT(*) FROM rated;

Это возвращает 10 507. WITH блок определяет шаг и называет его. SELECT под использует это имя. Ничего не было создано и ничего не сохранено, что является ответом на предварительный вопрос: rated существует только пока выполняется этот единственный запрос, и завтра коллега ничего не найдет.

Вся суть в этой временности. Вы получаете читаемость сохраненной таблицы без необходимости очистки, разрешений или устаревания, которые приносят реальная таблица.

Два правила синтаксиса охватывают практически все. Каждый именованный шаг заключен в скобки после AS. Несколько шагов разделяются запятыми, а слово WITH появляется только один раз, в самом верху.

CTE — это название шага, а не характеристики производительности. Большинство баз данных в любом случае выполняют одну и ту же работу. Вы получаете запрос, который можно прочитать, протестировать по частям и передать кому-нибудь другому.

2. Составление шагов и проверка каждого из них

Перед объяснением: ваш запрос состоит из трех последовательных шагов, и окончательный ответ неверен. Сколько запросов вам нужно выполнить, чтобы выяснить, какой шаг сломал проблему?

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

WITH rated AS (
    SELECT * FROM games WHERE TotalReviews >= 500
),
well_liked AS (
    SELECT * FROM rated WHERE PctPositive >= 90
)
SELECT COUNT(*) FROM well_liked;

Обратите внимание, что well_liked читает из rated, а не из games. Каждый шаг сужает предыдущий. Счет возвращается к 3454.

Теперь о привычке, которая делает это стоящим. Чтобы протестировать любой шаг, ничего не меняйте, кроме последней строки.

-- Same CTEs above, then one of:
SELECT COUNT(*) FROM rated;       -- 10,507
SELECT COUNT(*) FROM well_liked;  -- 3,454
SELECT * FROM well_liked LIMIT 5; -- eyeball actual rows

Четыре отсчета по цепочке подскажут вам, где число ошиблось, и это займет секунды. Шаг, который падает с 10 507 до 12, — это сломанный фильтр, и вы обнаружите его на шаге, а не в конце.

Прежде чем читать дальше, скажите вслух, почему тестирование третьего шага глубоко вложенного подзапроса сложнее, чем этот. Ответ заключается в том, что вложенный шаг не имеет имени, поэтому после него нечего ставить. FROM. Вам придется вырезать внутренний запрос, вставить его в другое место и надеяться, что вы взяли соответствующие скобки. Именно из-за этого трения люди пропускают чек.

3. Реальный пример четырех шагов

Перед пояснением: «пропущенная игра» не является столбцом ни в одном наборе данных. Как вы думаете, сколько отдельных решений нужно, чтобы превратить эту фразу в строки?

Четыре, и назвать их – это большая часть анализа. Вот собственно определение.

WITH rated AS (
    -- Step 1: enough reviews that the rating means something
    SELECT * FROM games WHERE TotalReviews >= 500
),
well_liked AS (
    -- Step 2: players actually rate it highly
    SELECT * FROM rated WHERE PctPositive >= 90
),
overlooked AS (
    -- Step 3: not many people own it
    SELECT * FROM well_liked WHERE EstOwnersMid <= 50000
),
genre_avg AS (
    -- Step 4: what "highly rated" means in its own genre
    SELECT PrimaryGenre, AVG(PctPositive) AS genre_rating
    FROM rated
    GROUP BY PrimaryGenre
    HAVING COUNT(*) >= 100
)
SELECT o.Name,
       o.PrimaryGenre,
       o.PctPositive,
       ROUND(g.genre_rating, 1) AS genre_avg
FROM overlooked AS o
JOIN genre_avg AS g ON o.PrimaryGenre = g.PrimaryGenre
ORDER BY o.PctPositive DESC;

Отсчет по цепочке: 82 956, затем 10 507, затем 3 454, затем 663. Каждая капля — это решение, с которым кто-то может поспорить, и это правильный аргумент, который можно иметь.

Четвертый шаг отличается от первых трех, и на это стоит обратить внимание. Это не сужает ряды. Он вычисляет среднее жанровое значение на основе rated шаг, а затем последний запрос снова присоединяется к нему. Используемый таким образом CTE представляет собой таблицу поиска, которую вы создаете на месте. Этот шаблон: вычисление сводки и последующее объединение ее с деталями — одна из самых полезных вещей, которые делают CTE.

Результаты говорят то, чего не может сказать исходный рейтинг. Кабуто Парк имеет положительный рейтинг 99,9% в жанре «Казуальный», средний показатель в жанре 84,6%. оборотень в действии имеет показатель 99,4%, тогда как средний показатель составляет 80,9%. Обе игры были невидимы в простом списке с самым высоким рейтингом, потому что простой список полон игр с сорока обзорами.

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

4. CTE для подзапроса, тот же ответ

Перед объяснением: обе версии ниже возвращают одинаковые строки. Если база данных в любом случае работает одинаково, имеет ли вообще значение выбор?

Вот вложенная версия первых двух шагов.

SELECT COUNT(*)
FROM (
    SELECT * FROM (
        SELECT * FROM games WHERE TotalReviews >= 500
    ) WHERE PctPositive >= 90
);

Те же 3454. Это правильно, и критика не в том, что это плохой стиль. Вы читаете его наизнанку, от средних скобок к краям, что противоположно порядку, в котором происходит работа. Ничто не имеет имени, поэтому ничто не может быть проверено без хирургического вмешательства.

Выбор имеет значение для человека, который будет читать его через три месяца, а это обычно вы. Вот и весь аргумент, и этого достаточно.

СитуацияДостичь
Две или более ступеньки, наложенные друг на другаCTE за шаг
Краткое изложение, к которому нужно вернуться к деталямA CTE
Шаг, который хочется пробежать и рассчитывать на негоA CTE
Одно маленькое условие внутри WHEREПодзапрос, встроенный
Шаг, используемый во многих различных запросах с течением времени.Представление, сохраненное в базе данных

Этот последний ряд является честной границей. CTE исчезает, когда запрос завершается. Если на следующей неделе троим людям понадобится один и тот же шаг, он принадлежит представлению или таблице, а не копируется в три файла.

5. Полное до и после

Тот же вопрос: у каких игр с высоким рейтингом очень мало владельцев по сравнению с их жанром?

До

SELECT Name, PrimaryGenre, PctPositive
FROM games
WHERE TotalReviews >= 500
  AND PctPositive >= 90
  AND EstOwnersMid <= 50000
ORDER BY PctPositive DESC;

Он запускается и возвращает 663 строки. Три проблемы, ни одна из которых не приводит к ошибке. Три порога объединены в один пункт без указания причины, поэтому никто не может сказать, какое решение было принято. Невозможно увидеть, сколько строк удалено по каждому условию. И он не может ответить на реальный вопрос, потому что для сравнения игры с ее жанром требуется среднее значение, которое этот запрос никогда не рассчитывает.

после

-- ============================================================
-- STEP 5: Which well-rated games are overlooked in their genre?
-- WHY: A plain top-rated list is dominated by games with forty
--      reviews. Each step below removes one specific problem,
--      and each has a row count you can check on its own.
-- COUNTS: 82,956 all games, 10,507 rated, 3,454 well liked,
--      663 overlooked. Change a threshold and rerun the counts.
-- ============================================================
WITH rated AS (
    --Games with enough reviews for the rating to be stable
    SELECT * FROM games WHERE TotalReviews >= 500
),
well_liked AS (
    --Of those, the ones players rate 90% positive or better
    SELECT * FROM rated WHERE PctPositive >= 90
),
overlooked AS (
    --Of those, the ones hardly anyone owns
    SELECT * FROM well_liked WHERE EstOwnersMid <= 50000
),
genre_avg AS (
    --Separately: the average rating within each genre,
    --  built from rated so it is not skewed by tiny samples
    SELECT PrimaryGenre, AVG(PctPositive) AS genre_rating
    FROM rated
    GROUP BY PrimaryGenre
    HAVING COUNT(*) >= 100
)
--Finally: the overlooked games, each shown next to its genre average
SELECT o.Name,
       o.PrimaryGenre,
       o.PctPositive,
       ROUND(g.genre_rating, 1) AS genre_avg
FROM overlooked AS o
JOIN genre_avg AS g ON o.PrimaryGenre = g.PrimaryGenre
ORDER BY o.PctPositive DESC;

Каждый порог теперь стоит рядом с его причиной. Каждый шаг можно посчитать. Сравнение жанров, о котором на самом деле задавался вопрос, присутствует. Формат комментариев тот же, что и у Как комментировать SQL, чтобы он обучал.

6. Крайние случаи и честные ограничения

Перед объяснением: вы разбиваете медленный запрос на шесть аккуратных CTE, и он становится медленнее. Какое отношение название шага может иметь к скорости?

Шесть вещей, которые стоит знать.

Некоторые базы данных рассматривают CTE как ограждение. Это ответ на предварительный вопрос. PostgreSQL до версии 12 материализовал каждый CTE, то есть рассчитывал каждый из них полностью, а не сворачивал его в более широкий план, поэтому фильтр в конечном запросе не мог перейти на предыдущий шаг. Современный PostgreSQL встраивает их по умолчанию, и вы можете принудительно настроить любое поведение с помощью AS MATERIALIZED или AS NOT MATERIALIZED. Если перезапись стала медленнее, это первое, на что следует обратить внимание.

CTE, на который ссылаются дважды, может быть вычислено дважды. Если CTE встроены, использование одного и того же шага в двух местах может привести к его повторному выполнению. Обычно не имеет значения. На шаге сканирования сорока миллионов строк, что немаловажно. Обработка больших наборов данных чехлы работают в этом размере.

Только один WITHи запятые между шагами. Письмо WITH опять же, перед вторым шагом здесь самая распространенная синтаксическая ошибка, и сообщение редко указывает на реальную строку.

Запятая перед финалом SELECT ломает его. Последний CTE не имеет запятой после закрывающей скобки. Это кусается каждый раз, когда вы меняете порядок шагов.

Название шага в честь реальной таблицы затеняет его. Если ты напишешь WITH games AS (...), каждое упоминание о games ниже теперь означает ваш шаг, а не стол. Иногда преднамеренно. Чаще полдень терял.

Рекурсивные CTE — это другой инструмент, носящий то же слово. WITH RECURSIVE позволяет шагу ссылаться на себя, и именно так вы перемещаетесь по иерархии, такой как цепочка отчетов сотрудников или дерево папок. У него общий синтаксис и почти ничего больше, и его стоит изучить отдельно, как только обычные CTE почувствуют себя автоматическими.

Почему это работает

Прирост читаемости — это не вопрос вкуса, и причина здесь в читателе, а не в базе данных. Рабочая память удерживает одновременно лишь несколько элементов, поэтому понимание ухудшается, когда читателю приходится одновременно держать в уме несколько неразгаданных фрагментов. Работа Свеллера по когнитивной нагрузке показывает, что материал, заставляющий вас удерживать и интегрировать отдельные элементы, налагает нагрузку, не связанную с реальной сложностью идеи, и что устранение этого разделения улучшает как понимание, так и передачу (Свеллер, 1988, Когнитивная наука, 12(2), 257–285). Вложенный подзапрос представляет собой именно такую ​​структуру. Вы держите в голове три открытые скобки, пытаясь найти самую внутреннюю. При присвоении имени шагу удерживаемый элемент заменяется словом, и нагрузка исчезает.

Повышение тестируемости достигается за счет чего-то более простого. Каждый именованный шаг сам по себе является отношением, поэтому к нему применяются те же операции, что и к любой таблице, включая COUNT. Это замыкание встроено в реляционную модель, а не добавлено синтаксисом CTE: каждая операция возвращает отношение, поэтому результаты можно без ограничений передавать в дальнейшие операции (Кодд, 1970, Коммуникации ACM, 13(6), 377–387). CTE дает этому свойству только имя, которое вы можете ввести.

Есть вторая причина, по которой эта страница продолжает просить вас ответить, прежде чем она объяснит. Учащиеся, которых просят объяснить себе проработанный пример по ходу дела, лучше понимают его и лучше применяют к новым задачам, чем учащиеся, которые читают тот же пример целиком (Чи, Басок, Льюис, Рейманн & Глейзер, 1989, Когнитивная наука, 13(2), 145–182).

Использование этого в вашем собственном проекте

Подумайте о самом длинном запросе, который вы написали. Не могли бы вы сказать, что делает его середина, не читая его целиком? Если нет, то это тот, который разделится первым.

Преобразование целого проекта запросов одновременно — это мрачная задача, и вы остановитесь на полпути. Вместо этого сделайте это по порядку.

  1. Сначала напишите шаги на английском языке, перед любым SQL. «Достаточно отзывов, затем хорошие оценки, затем мало владельцев». Если вы не можете перечислить шаги, значит, запрос не готов к записи.
  2. Дайте каждому шагу название, которое вы только что сказали. rated и well_liked бить cte1 и t2, которые являются именами, которые ничего не несут.
  3. Считайте каждый шаг по мере его добавления. Добавьте CTE, запустите SELECT COUNT(*) против него посмотрите на число, затем напишите следующий шаг. Поймать неисправный фильтр здесь стоит секунд.
  4. Запишите значения в файл в качестве комментария. Любой, кто повторно запустит его позже, сразу же сможет определить, сместились ли данные под ним.
  5. Продвигайте шаг к просмотру в тот момент, когда это понадобится второму запросу. Копирование CTE между файлами приводит к тому, что два определения одного и того же понятия начинают расходиться.

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

Подробнее об этом и еще что-нибудь подобное. Все инструкции находятся в одном месте на индекс направляющих: SQL, Tableau, миграция данных и связанные с ними рабочие привычки.

Все на одном экране

Это лист поиска. Закройте правую колонку, проработайте левую и произнесите каждый ответ вслух, прежде чем проверять его.

концепцияЧто он делает
CTEИменованный временный результат, определенный перед основным запросом и следующий за ним.
WITH name AS (...)Весь синтаксис. WITH появляется один раз вверху.
Несколько шаговРазделяются запятыми. Нет запятой перед финалом SELECT.
УкладкаБолее поздний шаг выбирает из предыдущего, образуя цепочку.
Тестирование шагаИзмените только последнюю строку на SELECT COUNT(*) FROM step.
Подвести итоги и присоединиться сноваПостройте среднее значение в CTE, а затем присоедините его к строкам с подробными сведениями.
Против подзапросаТот же результат. CTE читается сверху вниз и может быть проверен.
Против взглядаМнение сохраняется для всех. CTE существует для одного запроса.
производительностьОбычно один и тот же план. Старые версии PostgreSQL изолированы CTE, что может замедлить перезапись.
AS MATERIALIZEDПринудительно вычисляет и удерживает шаг один раз в PostgreSQL 12 и более поздних версиях.
СлежениеПрисвоение CTE имени реальной таблицы скрывает эту таблицу для остальной части запроса.
WITH RECURSIVEШаг, который относится сам к себе. Для иерархий. Отдельная тема.
МыПроизнесите шаг на английском языке, а затем используйте эти слова. Нет cte1.
ПривычкаСчитайте каждый шаг по мере его добавления, прежде чем писать следующий.
Единственная привычка, которую следует сохранить. Если вы больше ничего не берете с этой страницы, запустите SELECT COUNT(*) на каждом шаге, пока вы это пишете. Цепочку стоит строить только потому, что ее можно проверить наполовину, а проверка занимает секунды. Если запрос прерывается таким образом, который не описан на этой странице, существует общая шлейф диагностики на предмет застревания.

Еще одна мысль, и мне бы искренне хотелось услышать ответы других людей. Разделение моего запроса на именованные шаги изменило анализ, а не только форматирование: однажды rated имело название, было очевидно, что среднее значение жанра следует строить на основе него, а не на основе всего каталога, и что один выбор повлиял на результаты. Изменило ли название шага то, что вы решили вычислить?

Ссылки

Запрос состоит из четырех подзапросов, и вы потеряли представление о том, что возвращает каждый из них.

SQL для аналитиков состоит из 458 страниц, CTE создаются шаг за шагом, поэтому длинный запрос читается сверху вниз, а не наизнанку.

SQL для аналитиков, 19 $ →
Потренируйтесь строить запросы поэтапно.

тот Курс SQL запускает SQLite в браузере, поэтому вы добавляете шаг, подсчитываете его и добавляете следующий. Рядом с ним лежат рабочие примеры, карточки и пробный экзамен. Ничего устанавливать, учетная запись не требуется.

Откройте набор SQL →