← Все курсы · Курс SQL

RANK против DENSE_RANK против ROW_NUMBER в SQL

Майкл Носито · Обновлено в августе 2026 г. · Каждый запрос на этой странице был выполнен до его публикации.

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

Что вы делаете, это выбираете по вопросу. «Дайте мне три ряда» — это ROW_NUMBER. «Дайте мне всех, кто находится на первых трёх позициях» — это RANK. «Дайте мне всех, кто набрал три лучших балла» — это DENSE_RANK. В оставшейся части этой страницы показано, как работает каждый из них, поэтому вы можете наблюдать за изменением количества строк.

Краткая версия. Все три числовых ряда по порядку. Они расходятся во мнениях только по поводу связей.

Five rows of sales figures, 12, 12, 9, 9 and 7, numbered by each of the three functions side by side. ROW_NUMBER gives 1, 2, 3, 4, 5 and never repeats a number. RANK gives 1, 1, 3, 3, 5, repeating a number on each tie and then skipping the numbers the tie used up, so 2 and 4 never appear. DENSE_RANK gives 1, 1, 2, 2, 3, repeating a number on each tie and skipping nothing. deals ROW_NUMBER RANK DENSE_RANK 12 12 9 9 7 12345 11335 11223 24
Две ничьи, три ответа. Зачеркнутые цифры — это те, которые РАНГ пропускает.

Таблица, на которой выполняются примеры

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

CREATE TABLE sales(rep TEXT, region TEXT, deals INT, signed_on TEXT);

INSERT INTO sales VALUES
('Alvarez','North',12,'2026-07-02'),
('Brennan','North',12,'2026-07-05'),
('Cho',    'North', 9,'2026-07-01'),
('Diaz',   'North', 9,'2026-07-09'),
('Ellis',  'North', 7,'2026-07-11'),
('Fowler', 'South',15,'2026-07-03'),
('Grant',  'South',11,'2026-07-06'),
('Hale',   'South',11,'2026-07-08'),
('Iyer',   'South',11,'2026-07-12'),
('Jansen', 'South', 4,'2026-07-14');

У Севера есть две связи. Альварес и Бреннан закрыли по 12 сделок, а Чо и Диас закрыли по 9. Саут имеет тройную ничью с 11 сделками. Эти связи и являются темой этой страницы.

Все трое рядом, в одном ряду

Прежде чем вы прочитаете результат: Альварес и Бреннан закрыли 12. Какое число, по вашему мнению, даст каждая функция Чо, который закрыл 9?

SELECT rep, deals,
       ROW_NUMBER() OVER (ORDER BY deals DESC) AS rn,
       RANK()       OVER (ORDER BY deals DESC) AS rk,
       DENSE_RANK() OVER (ORDER BY deals DESC) AS dr
FROM sales
WHERE region = 'North';
Представительсделкиrnrkdr
Альварес12111
Бреннан12211
Давать9332
Диас9432
Эллис7553

Прочитайте каждый столбец, и правило сформулирует себя.

ROW_NUMBER раздает от 1 до 5 и никогда не повторяется. Он должен дать Альваресу и Бреннану разные числа, поэтому выбирает одного из них вторым, причем выбирает произвольно. Запустите тот же запрос завтра, и Бреннан окажется первым.

RANK дает обеим 12 1, затем переходит к 3 для Чо. 2 пропало, потому что ничья уже использовала это место. Чо действительно третий представитель в списке.

DENSE_RANK дает обеим 12-кам 1, затем дает Чо 2. Он подсчитывает количество отдельных сделок, а не рядов. На Севере существует три графа, поэтому его число останавливается на 3.

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

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

Используйте ROW_NUMBER, если вам нужна ровно одна строка на группу.

Во-первых: вам нужен один лучший представитель в каждом регионе. Какой из трех вы можете использовать безопасно?

Только ROW_NUMBER, потому что он единственный, который обещает одну строку с номером 1. Пронумеруйте строки внутри каждой области, а затем оставьте строки с номером 1.

SELECT rep, region, deals
FROM (
  SELECT rep, region, deals,
         ROW_NUMBER() OVER (PARTITION BY region
                            ORDER BY deals DESC, signed_on) AS rn
  FROM sales
) numbered
WHERE rn = 1;
ПредставительРегионсделки
АльваресСевер12
ФаулерЮг15

Там работают две вещи. PARTITION BY region перезапускает нумерацию для каждого региона, так что оба региона получают свою собственную 1. И signed_on является тай-брейком. Альварес и Бреннан закрыли по 12, так что все решает более ранняя дата подписания.

Без этого второго столбца сортировки победителем становится та строка, которую движок выдал первой, и она может меняться между запусками. Так: всегда давай ROW_NUMBER тай-брейк. Подойдет любой столбец, который не может повторяться, и первичный ключ является самым безопасным. Это наиболее распространенный дефект в реальных запросах дедупликации. Они бегут, возвращают нужное количество строк и каждый раз спокойно выбирают другую строку.

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

Используйте RANK, когда связанные строки заслуживают одного и того же места.

Перед запросом: три представителя на юге заключили 11 сделок. Должна ли таблица лидеров назвать одного из них четвертым?

Нет, и вот что RANK это для. Это правило медали. Два золота означают отсутствие серебра.

SELECT rep, region, deals
FROM (
  SELECT rep, region, deals,
         RANK() OVER (PARTITION BY region ORDER BY deals DESC) AS rk
  FROM sales
) ranked
WHERE rk <= 3
ORDER BY region, deals DESC;

Это возвращает 8 рядов, а не 6.

ПредставительРегионсделкиМесто
АльваресСевер121
БреннанСевер121
ДаватьСевер93
ДиасСевер93
ФаулерЮг151
ГрантЮг112
ХейлЮг112
АйерЮг112

Норт делает четыре повторения, потому что обе его пары равны. Юг вносит четыре, потому что все три представителя из 11 занимают второе место. Эллис и Янсен — единственные два представителя, которые пропускают этот фильтр.

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

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

Используйте DENSE_RANK, если вам нужны первые N значений.

Во-первых: сколько различных сделок имеется на Севере и сколько на Юге?

Три на севере, это 12, 9 и 7. Три на юге, это 15, 11 и 4. DENSE_RANK нумерует эти значения, а не строки, поэтому запрашивает dr <= 3 спрашивает для всех, кто находится на трех верхних значениях.

SELECT rep, region, deals
FROM (
  SELECT rep, region, deals,
         DENSE_RANK() OVER (PARTITION BY region ORDER BY deals DESC) AS dr
  FROM sales
) ranked
WHERE dr <= 3
ORDER BY region, deals DESC;

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

Вот три функции с одним и тем же фильтром, по одной строке каждая.

ФункцияФильтрСтроки возвращеныТо, что вы просили
ROW_NUMBERrn <= 36Три строки на регион
RANKrk <= 38Все в тройке лидеров
DENSE_RANKdr <= 310Все, кто входит в тройку крупнейших сделок, имеют значение

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

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

Пограничные случаи, которые стоит знать

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

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

PARTITION BY является необязательным. ORDER BY нет. Пропустить PARTITION BY и вы ранжируете всю таблицу, а это зачастую именно то, что вам нужно. Пропустить ORDER BY и цифры ничего не значат, потому что ничто не решает, что означает первое.

Еще двое в одной семье. NTILE(4) разбивает отсортированные строки на четыре сегмента и помечает каждую строку соответствующим сегментом, таким образом строятся квартили. PERCENT_RANK() дает место в виде дроби от 0 до 1, что вам нужно, когда две группы имеют разные размеры и необработанное место не может сравниваться.

Поддержка сейчас универсальна, но не древняя. Функции ранжирования появились в SQL Server 2005, PostgreSQL 8.4, MySQL 8.0 и SQLite 3.25. Если ROW_NUMBER выдает синтаксическую ошибку, проверьте версию, прежде чем проверять синтаксис.

Почему числа ведут себя таким образом

Три функции — это не три версии одной идеи. Они считают три разные вещи, и названия говорят, какие именно.

ROW_NUMBER считает строки. Пять рядов, пять цифр, повтор невозможен.

RANK сообщает место, равное единице плюс количество обыгравших вас рядов. Ничто не сравнится с Альваресом или Бреннаном, поэтому оба имеют 1. Два ряда превосходят Чо, поэтому Чо равен 3. Пропущенная 2 выпадает из этого определения. Это не отдельное правило, которое следует запомнить.

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

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

Как применить это в своей работе

  1. Найдите в своих файлах один запрос, который возвращает первое N. Посмотрите на столбец сортировки и спросите, могут ли в нем связать две строки.
  2. Если есть возможность, добавьте в запрос все три функции и запустите его один раз. Столбец, который не совпадает с остальными, расскажет вам, что на самом деле делает ваш текущий запрос.
  3. Решайте по вопросу, а не по коду. Фиксированное количество строк ROW_NUMBER. Место это RANK. Высшие ценности DENSE_RANK.
  4. Дайте каждому ROW_NUMBER вы держите колонку тай-брейка в своей ORDER BY, даже если сегодня ничья кажется невозможной.
  5. Не проверяйте каждый ранжирующий запрос в базе кода на этой неделе. Эта работа жалкая и по большей части бесполезная. Исправляйте их по мере их появления, начиная с того, что человек считает стоящим.

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

Шпаргалка

ФункцияНа галстуке1, 1, тогда?Доберитесь до этого, когда
ROW_NUMBER()Разбивает его произвольно1, 2, 3Вам нужно ровно N строк или по одной строке на группу.
RANK()Тот же номер, затем пропуски1, 1, 3Место имеет значение, а связанные строки имеют одно общее значение.
DENSE_RANK()Тот же номер, без пропуска1, 1, 2Вам нужны верхние N значений, сколько бы строк ни было
NTILE(n)Наполняет ведра по порядкуЭтикетки для ведерКвартили, децили, группы даже по размеру
PERCENT_RANK()Связанные строки имеют общую долюот 0 до 1Сравнение мест в группах разного размера

Единственная привычка, которую нужно сохранить

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

Какой из трех запросов вы используете чаще всего, и выбрали ли вы его или это был первый, который вы выучили?

Каждый номер здесь был проведён до того, как был опубликован. Десять строк выше — это весь набор данных, поэтому вы можете вставить CREATE TABLE в любой сеанс SQLite, PostgreSQL или MySQL и воспроизведите 6, 8 и 10 самостоятельно.
Здесь вы выбрали правильную функцию. Следующий вопрос – какие из этих цифр вообще относятся к отчету.

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

SQL для аналитиков, 19 $ →
Введите их самостоятельно, по одному.

тот Курс SQL охватывает SELECT, WHERE, JOIN и GROUP BY с отработанными примерами, практикой и пробным экзаменом, и все это в браузере. Более широкое семейство оконных функций, включая текущие итоги и LAG, находится в Оконные функции SQL.

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

Или получите повторения: открыть SQL-практикум и вводите по одному исполняемому запросу за раз.