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

Оконные функции SQL: как получить верхнюю строку для каждой группы

Добавляйте числа в каждую строку, не теряя ни одного из них · Написано для прослушивания · Часть Analyst Prep Kit

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

К концу этой страницы вы сможете ответить на вопрос, который останавливает большинство людей в первую неделю написания SQL: какая строка является лучшей в каждой категории. Вы будете знать OVER и PARTITION BY, три функции ранжирования и то, как каждая из них учитывает ничью, промежуточный итог и LAG для сравнения строки с предыдущей. Это около двадцати пяти минут.

Вот что на самом деле с этим делать. В следующий раз, когда ты напишешь GROUP BY genre и верните лучший рейтинг без прикрепленного к нему имени, хватит переписывать GROUP BY. Добавлять ROW_NUMBER() OVER (PARTITION BY genre ORDER BY rating DESC) Вместо этого перейдите к простому запросу, а затем оставьте строки с номером 1. Это весь ход, и он заменяет запрос, который большинство людей никогда не выполняет.

Краткая версия: оконная функция добавляет вычисляемый столбец в каждую строку, оставляя каждую строку на месте. Группировка сворачивает строки. Окно смотрит на них.

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

Grouping collapses rows, a window keeps them Two panels side by side, each starting from the same stack of four identical row shapes. The left panel is labelled GROUP BY. Its four rows funnel down through a single arrow into one row at the bottom, and the four original rows are shown faded to indicate they are gone from the result. Only one row remains. The right panel is labelled OVER. Its four rows stay exactly where they are, at full strength, and each one gains a small badge on its right hand side holding a number: one, two, three, four. Nothing funnels and nothing is faded. The contrast is the whole idea: the left panel ends with a single summary row and no way to say which original row it came from, while the right panel ends with all four rows still present, each carrying its own calculated value. GROUP BY OVER 1 2 3 4
Те же строки, та же арифметика. Слева ряды израсходованы. Справа они все еще там, каждый несет свой ответ.
Что вы узнаете
  1. Что такое оконная функция в одном запросе
  2. Верхний ряд в каждой группе
  3. Связи и три функции ранжирования
  4. Текущий итог и изменение по сравнению с прошлым годом
  5. Полное до и после
  6. Пограничные случаи и честные ограничения
  7. Почему это работает
  8. Использование этого в вашем собственном проекте
  9. Все на одном экране
Проработанный пример реальный. Все цифры на этой странице взяты из опубликованного портфолио проекта: поиска действительно игнорируемых игр в каталоге Пар, насчитывающем 125 855 штук, по адресу Скрытые сокровища Пар на GitHub. Фильтры оставляют 175 игр. Эта страница начинается с этого списка и задает вопросы, которые GROUP BY не могу ответить. Если группировка еще новая, читайте GROUP BY и HAVING во-первых, потому что эта страница является ее продолжением.

Каждый запрос ниже читается из gems, который представляет собой отфильтрованный список: не менее 2000 отзывов, 95 % из которых положительные или выше, цена 20 или ниже и один из четырех самых низких уровней владельца. Он содержит 175 строк.

1. Что такое оконная функция в одном запросе

Перед пояснением: вы пишете запрос на 175 строк и добавляете COUNT(*) OVER () в список выбора. Сколько строк возвращается?

Все 175, каждый показывает число 175. В этом вся идея оконной функции в одном результате. Он что-то вычислял по строкам, не удаляя ни одну из них.

SELECT Name,
       PctPositive,
       COUNT(*) OVER () AS total_gems
FROM gems;

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

Сравните это с версией без OVER. SELECT COUNT(*) FROM gems возвращает одну строку, содержащую 175, а имена исчезают. Слово «окно» — это картинка, за которую нужно держаться. Каждая строка может просматривать набор других строк и сообщать что-то о них, при этом просмотр ничего не требует.

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

OVER (
    PARTITION BY PrimaryGenre   -- which rows are in this row's window
    ORDER BY PctPositive DESC   -- in what order to consider them
)                               -- (a frame can narrow it further, section 6)

PARTITION BY разбивает строки на отдельные окна, по одному на каждое значение. Это та же идея, что и GROUP BY, с одним отличием, которое имеет большее значение, чем любое другое: оно ничего не разрушает.

Каждая оконная функция представляет собой обычную функцию плюс OVER. Ты уже знаешь COUNT, SUM, AVG, MIN и MAX. Добавлять OVER (...) и каждый из них перестает сворачивать строки и начинает их аннотировать. Функции ранжирования такие, как ROW_NUMBER являются единственными, которые существуют исключительно для Окна.

2. Верхний ряд в каждой группе

Перед объяснением: SELECT PrimaryGenre, MAX(PctPositive) FROM gems GROUP BY PrimaryGenre дает вам лучший рейтинг в каждом жанре. Что мешает вам добавить Name в этот список выбора, чтобы узнать, какая это была игра?

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

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

WITH ranked AS (
    SELECT Name,
           PrimaryGenre,
           PctPositive,
           TotalReviews,
           ROW_NUMBER() OVER (
               PARTITION BY PrimaryGenre
               ORDER BY PctPositive DESC, TotalReviews DESC
           ) AS rn
    FROM gems
)
SELECT PrimaryGenre, Name, PctPositive, TotalReviews
FROM ranked
WHERE rn = 1
ORDER BY PctPositive DESC;

Прочтите OVER скобки вслух как предложение: в каждом жанре расставьте игры по наивысшему рейтингу и пронумеруйте их, начиная с единицы. Каждой из 175 строк присвоен номер. Лучшая приключенческая игра — 1, вторая лучшая — 2, а лучшая экшн-игра — тоже 1, потому что PARTITION BY возобновляет подсчет для каждого жанра.

Вот реальный результат.

ЖанрИгра с лучшим рейтингомПозитивныйОтзывы
ПриключениеЗамок, полный кошек99.4%4,009
ДействиеПеревернутый99.3%2,364
ПовседневныйПарабокс Патрика99.2%4,472
Индия подниму тебя98.6%4,037
RPGПуть Ахры98.4%2,578
МоделированиеАвиасборка97.1%4,112

тот WHERE rn = 1 должен сидеть снаружи, на втором этапе, и стоит знать причину, потому что это самая распространенная ошибка, с которой здесь сталкиваются. Оконные функции выполняются после WHERE, поэтому столбец rn еще не существует, когда WHERE оценивается. Письмо WHERE ROW_NUMBER() OVER (...) = 1 это ошибка в каждом двигателе. Оберните запрос в CTE, а затем отфильтруйте CTE. именованные шаги вот как вы получаете второе место для фильтрации.

Скажи вслух, почему ORDER BY PctPositive DESC LIMIT 1 не смог бы создать эту таблицу, пока вы не прочитали дальше. Ответ в том, что LIMIT сокращает весь результат и дает вам одну игру в целом, а не по одной игре на каждый жанр. PARTITION BY это LIMIT который перезапускается для каждого значения, и это то, что обычный SQL не имеет другого способа выразить.

Измените одно слово, и тот же запрос ответит на другой вопрос. WHERE rn <= 3 дает тройку лучших по жанру. Эта форма, число внутри и фильтрация снаружи, охватывает большую часть того, для чего людям действительно нужны оконные функции.

Представьте, что вы запускаете это на столе, с которым работаете. Разделите по клиентам, упорядочите по дате и сохраните rn = 1. Что вы только что построили и сколько строк вернулось?

3. Ничьи и три функции ранжирования

Перед пояснением: обе инди-игры имеют рейтинг ровно 96,6%. Если пронумеровать ряды, в каком из них получится 7, а в каком — 8?

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

Все три числовых ряда. Они отличаются только тем, как обрабатывают равные значения.

SELECT Name,
       PctPositive,
       ROW_NUMBER()  OVER (ORDER BY PctPositive DESC) AS rn,
       RANK()        OVER (ORDER BY PctPositive DESC) AS rk,
       DENSE_RANK()  OVER (ORDER BY PctPositive DESC) AS dr
FROM gems
WHERE PrimaryGenre = 'Indie'
ORDER BY PctPositive DESC;

Интересная часть этого результата — четыре ряда вокруг галстука.

ИграПозитивныйROW_NUMBERRANKDENSE_RANK
новая жизнь.96.6%777
Кобальтовое ядро96.6%877
неисправность - веха, вторая сторона: выше96.3%998
Позже Аллигатор96.3%1098

Три поведения, и таблица — все объяснение. ROW_NUMBER никогда не повторяется и никогда не пропускает, поэтому это разделяет ничью. RANK дает связанным строкам одинаковое количество, а затем пропускает, поэтому 8 отсутствует. DENSE_RANK дает связанным строкам одинаковое количество и не пропускает, поэтому учитываются отдельные значения, а не строки.

Выбор между ними – это не вопрос стиля, это зависит от того, для чего нужен номер.

Ты хочешь Использовать Потому что
Ровно одна строка на группуROW_NUMBERЭто никогда не повторяется, поэтому rn = 1 возвращает одну строку
Все, кто первым сделал ничьюRANKОбе связанные строки равны 1, поэтому rk = 1 возвращает оба
Три верхних уровня рейтингаDENSE_RANKОн подсчитывает значения, поэтому dr <= 3 означает три различных рейтинга

Одна привычка убирает произвол из ROW_NUMBER навсегда. Добавьте тай-брейк в ORDER BY, поэтому порядок полностью определяется данными. Поэтому использован раздел 2. ORDER BY PctPositive DESC, TotalReviews DESC. Когда две игры имеют одинаковый рейтинг, побеждает та, у которой больше обзоров, и запрос возвращает один и тот же ответ при каждом запуске. Запрос, результат которого меняется между запусками, — это ошибка, которая пока не замечена.

4. Текущая сумма и изменение по сравнению с прошлым годом.

Перед объяснением: добавьте ORDER BY ReleaseYear внутри OVER скобки а SUM. Изменяет ли это сумму или только порядок ее отображения?

Это меняет сумму, и это самая удивительная вещь в окнах. Без ORDER BY, окно представляет собой весь раздел, и в каждой строке отображается одно и то же общее количество. Добавление ORDER BY заставляет окно расти по мере продвижения: окно каждой строки - это все, вплоть до этой строки. Сумма становится промежуточной.

SELECT ReleaseYear,
       COUNT(*) AS gems_that_year,
       SUM(COUNT(*)) OVER (ORDER BY ReleaseYear) AS running_total,
       COUNT(*) - LAG(COUNT(*)) OVER (ORDER BY ReleaseYear) AS change_vs_prior
FROM gems
GROUP BY ReleaseYear
ORDER BY ReleaseYear;

Вот последние семь лет этого результата.

Год выпускаДрагоценные камни того годаПромежуточная суммаИзменение по сравнению с предыдущим годом
20191560+1
20202686+11
202117103-9
202222125+5
202316141-6
202424165+8
202510175-14

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

LAG — это другая функция в этом запросе, и она делает одну небольшую вещь. Он возвращается к предыдущей строке в том же окне и возвращает из нее значение. LAG(x) на один ряд назад, LAG(x, 3) на три ряда назад, и LEAD(x) вместо этого тянется вперед. Вычтите и получите изменение. За первым рядом ничего нет, поэтому LAG возвращает NULL там, и вычитание дает NULL а не ноль. Использовать LAG(x, 1, 0) если вы хотите вместо этого использовать значение по умолчанию.

Этот запрос также показывает то, что люди с первого раза находят странным, и это намеренно законно. SUM(COUNT(*)) является агрегатом внутри оконной функции. Это работает, потому что они запускаются в разное время. COUNT(*) сначала сжимает строки в одну по году, а затем оконная функция обрабатывает эти годовые строки. Вы просматриваете сводку, а не необработанную таблицу.

Падение на 14 в 2025 году — это тот показатель, с которым следует быть осторожным, и это не тенденция. Набор данных представляет собой снимок, сделанный в середине 2025 года, поэтому неполный год сравнивается с полным. Число, имеющее такое обычное объяснение, — это именно то число, которое читатель вам процитирует, поэтому напишите рядом с ним причину. Документирование такого ограничения это привычка, которая стоит больше, чем вопрос.

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

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

До

SELECT PrimaryGenre,
       MAX(PctPositive) AS best_rating,
       COUNT(*) AS games
FROM gems
GROUP BY PrimaryGenre
ORDER BY best_rating DESC;

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

после

-- ============================================================
-- STEP 7: The best rated game in each genre, in context.
-- WHY: MAX() gives the best rating but destroys the row it came
--      from, so the game's name cannot come with it. Numbering
--      the rows keeps every row and marks the winner instead.
-- CHECK: rn = 1 must return one row per genre. Nine genres in,
--      nine rows out. The tiebreaker on TotalReviews makes that
--      result stable between runs.
-- ============================================================
WITH ranked AS (
    --Number the games within each genre, best rated first.
    --  TotalReviews breaks ties so the order is fully decided.
    SELECT Name,
           PrimaryGenre,
           PctPositive,
           TotalReviews,
           ROW_NUMBER() OVER (
               PARTITION BY PrimaryGenre
               ORDER BY PctPositive DESC, TotalReviews DESC
           ) AS rn,
           --How many gems the genre has, on every row of it
           COUNT(*) OVER (PARTITION BY PrimaryGenre) AS genre_size,
           --The genre's average, sitting next to the game's own
           AVG(PctPositive) OVER (PARTITION BY PrimaryGenre) AS genre_avg
    FROM gems
)
--Keep the winner of each genre. The context columns came along.
SELECT PrimaryGenre,
       Name,
       PctPositive,
       genre_size,
       ROUND(genre_avg, 1) AS genre_avg
FROM ranked
WHERE rn = 1
ORDER BY PctPositive DESC;

Три оконные функции используют один раздел, и ни одна из них не требует дополнительного прохода по таблице или дополнительного соединения. Название сохраняется, рейтинг игры сохраняется, и сравнение с ее жанром оказывается в том же ряду. В Приключение входит 75 из 175 игр, а в Моделирование — 2, что меняет ценность любого из победителей этого жанра. Формат комментариев тот же, что и у Как комментировать SQL, чтобы он обучал.

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

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

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

Связи в ORDER BY вытяните будущие ряды в окно. Это ответ на предварительный вопрос, и это самая тонкая ошибка на этой странице. По умолчанию окно с ORDER BY охватывает все, от начала раздела до последней строки, связанной с текущей. Две строки относятся к одной и той же дате, поэтому обе показывают итоговую сумму, включая обе. Исправить это можно одной строкой: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW подсчитывает строки, а не связанные значения. Эта линия называется рамкой и представляет собой третью часть OVER скобки.

Вы не можете фильтровать функцию окна в WHERE. Окна рассчитываются после WHERE и после GROUP BY, поэтому столбец еще не существует. Оберните его в CTE и отфильтруйте снаружи. В сообщении об ошибке обычно указывается название функции, а не поясняется порядок ее выполнения, поэтому людям приходится тратить на это час.

PARTITION BY не GROUP BY, и их смешивание показывает. Группировка меняет количество строк, которые вы получаете обратно. Разделение никогда не происходит. Если количество строк изменилось, GROUP BY сделал это.

NULL В ORDER BY сортируется по-разному в зависимости от движка. PostgreSQL помещает NULL последним в порядке возрастания, SQLite и MySQL ставят его первым. Если раздел может содержать NULL в столбце, в котором вы занимаете место, скажите, что вы хотите, с помощью NULLS FIRST или NULLS LAST там, где ваш движок это поддерживает, вместо того, чтобы принимать значение по умолчанию и удивляться следующему движку.

Окно не может видеть строки, которые WHERE уже удалил. Фильтровать по одному жанру и COUNT(*) OVER () учитывается только этот жанр. Обычно это то, чего вы хотите. Если это не так, сначала выполните оконную обработку в CTE, а затем отфильтруйте.

Сам список имеет ограничение по данным, которое вам покажет рейтинг. Портал 2 находится в этом списке с 155 440 отзывами и оценкой владельцев от 0 до 20 000, что не может быть правдой. Уровни владельцев основаны на приблизительной оценке, и она неверна для некоторых старых популярных игр. Ранжирование позволяет легко обнаружить ошибки такого рода, поскольку неправдоподобная строка поднимается наверх раздела, где ее невозможно пропустить. Сортировка ваших данных — один из самых дешевых способов обнаружить, что они грязные.

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

Причина, по которой оконная функция может делать что-то без предварительной договоренности GROUP BY может является скорее структурным, чем вопросом удобства. Группировка определяется так, чтобы возвращать одну строку на группу, поэтому отдельные строки вообще не представлены в результате, и любое значение, принадлежащее одной строке, невозможно восстановить из нее. Реляционная модель Кодда построена на операциях, которые принимают отношения и возвращают отношения, что позволяет без ограничений использовать результаты в дальнейших операциях (Кодд, 1970, Коммуникации ACM, 13(6), 377–387). Группировка — одна из таких операций, и она задумана с потерями. Оконная функция — это операция, которая аннотирует вместо сокращения, поэтому существуют обе функции, а не одна из них является лучшей версией другой.

Эффективность также не случайна. В разделе 5 требуется указать рейтинг, подсчет и среднее значение по одному и тому же разделу, и база данных может ответить на все три вопроса за один проход отсортированных данных. Лейс, Кундхиканджана, Кемпер и Нойманн описали, как механизмы оценивают оконные функции, сортируя один раз для каждого отдельного раздела и упорядочивая спецификацию, а затем просматривая строки для создания каждой функции, которая ее разделяет (Лейс, Кундхиканджана, Кемпер и Нейманн, 2015, Труды Фонда ВЛДБ, 8(10), 1058–1069). Стоит помнить о практических последствиях: повторное использование одного и того же OVER пункт близок к бесплатному, а четвертое окно с другим PARTITION BY может означать другой сорт. Написание одних и тех же скобок три раза — дешевый вариант, а не ленивый.

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

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

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

Делайте это в таком порядке.

  1. Сначала напишите простой запрос, вообще без окна. Выводите на экран нужные вам строки, по одной на каждую вещь.
  2. Прежде чем печатать, произнесите слово «окно» вслух. «В каждом жанре, в порядке рейтинга, сначала самый высокий». Эти слова являются OVER скобки по порядку.
  3. Добавьте оконную функцию и просмотрите все строки. Пока не фильтруйте. Убедитесь, что нумерация возобновляется там, где вы ожидаете, поскольку отсутствует PARTITION BY выглядит нормально, пока вы не заметите, что числа достигают 1 только один раз.
  4. Добавьте тай-брейк в ORDER BY поэтому два прогона не могут расходиться во мнениях. Подойдет любой столбец с разными значениями.
  5. Оберните его в CTE и отфильтруйте снаружи. WHERE rn = 1 всегда живет во внешнем запросе.
  6. Сосчитайте результат по количеству групп. Девять жанров должны дать девять строк. Эта одна проверка выявляет почти все ошибки на этой странице.

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

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

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

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

концепцияЧто он делает
оконная функцияВычисляет по набору строк и помещает ответ в каждую строку. Не удаляет ни один из них.
OVER (...)Слово, которое делает любую функцию оконной. В скобках указано, какие строки.
OVER ()Пустые скобки. Окно — это каждая строка результата.
PARTITION BYРазбивает строки на отдельные окна, по одному на каждое значение. Никогда не меняет количество строк.
ORDER BY внутри OVERЗаставляет окно расти построчно. Вот что превращает SUM в промежуточную сумму.
ROW_NUMBER()1, 2, 3 без повторов и пропусков. Разрезает связи произвольно.
RANK()Связи разделяют номер, а затем он пропускает. 1, 1, 3.
DENSE_RANK()Ничьи разделяют номер, без пропуска. 1, 1, 2. Подсчитывает различные значения.
Верхний ряд на группуНомер с ROW_NUMBER в CTE, то WHERE rn = 1 снаружи.
Тройка лучших в группеТот же запрос с WHERE rn <= 3.
Промежуточная суммаSUM(x) OVER (ORDER BY date). Его последнее значение должно равняться простой сумме.
LAG(x) и LEAD(x)Значение из предыдущей или следующей строки окна. NULL по краям.
Год за годомx - LAG(x) OVER (ORDER BY year).
рамкаROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Считает ряды, поэтому связи перестают просачиваться.
Фильтрация окнаНевозможно в WHERE. Окна запускается после него. Используйте CTE.
СтабильностьВсегда добавляйте тай-брейк к ORDER BY, или два прогона могут не согласиться.
ЗатратыПовторное использование одного OVER статья почти бесплатна. Другой PARTITION BY может означать другой сорт.
Единственная привычка, которую следует сохранить. Если вы больше ничего не извлекаете из этой страницы, посчитайте результат запроса топ-группы с количеством групп, прежде чем поверить этому. Девять жанров, девять рядов. Пропавший без вести PARTITION BY и ничья, приведшая к двум победителям, оба отображаются в этом одном числе, и ни один из них не вызывает ошибки. Если запрос прерывается таким образом, который не описан на этой странице, существует общая шлейф диагностики на предмет застревания.

Еще одна мысль, и мне бы искренне хотелось услышать ответы других людей. Тай-брейк – это тот момент, в котором я долгое время ошибался. Мой запрос по топ-жанру выглядел завершенным и при повторном запуске выдал несколько иной победитель, и я винил в этом данные, прежде чем понял это. ROW_NUMBER просто был свободен в выборе. Посылался ли вам когда-либо нестабильный запрос и что вас предупредило?

Ссылки

Вам нужен ранг для каждой группы, и GROUP BY сворачивает строки, которые вы хотели сохранить.

SQL для аналитиков составляет 458 страниц, OVER, PARTITION BY и кадр читается по одному предложению за раз, с выводом на каждом шаге.

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

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

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