Статьи

SQL-трюк: row_number () — это ВЫБРАТЬ, что за плотным_ранком () — ВЫБРАТЬ ОТЛИЧИТЬ

Стандартные функции ранжирования SQL: 2003 время от времени являются отличными компаньонами и полезными инструментами. Те, которые поддерживаются почти во всех базах данных:

  • ROW_NUMBER (): этот генерирует новый номер строки для каждой строки, независимо от дубликатов в разделе.
  • RANK (): генерирует новый номер строки для каждой отдельной строки, оставляя пробелы между группами дубликатов внутри раздела.
  • DENSE_RANK (): этот генерирует новый номер строки для каждой отдельной строки, не оставляя пробелов между группами дубликатов в пределах раздела.

Как всегда, вышесказанное гораздо проще понять на примере. Давайте предположим следующую схему PostgreSQL, содержащую таблицу с 8 записями, некоторые из которых являются дубликатами:

1
2
3
4
5
6
7
8
9
CREATE TABLE t AS
SELECT 'a' v UNION ALL
SELECT 'a'   UNION ALL
SELECT 'a'   UNION ALL
SELECT 'b'   UNION ALL
SELECT 'c'   UNION ALL
SELECT 'c'   UNION ALL
SELECT 'd'   UNION ALL
SELECT 'e'

Теперь давайте выберем каждое значение вместе с тремя вышеупомянутыми функциями ранжирования. А для пинки и хихиканья мы будем использовать стандартное предложение SQL для WINDOW ! Да, это спасло нас от 15 символов повторяющегося кода SQL. Обратите внимание, что предложение WINDOW вряд ли реализовано отдельно PostgreSQL и Sybase SQL Anywhere…

1
2
3
4
5
6
7
8
SELECT
  v,
  ROW_NUMBER() OVER (w) row_number,
  RANK()       OVER (w) rank,
  DENSE_RANK() OVER (w) dense_rank
FROM t
WINDOW w AS (ORDER BY v)
ORDER BY v

И вышеупомянутые результаты в:

01
02
03
04
05
06
07
08
09
10
11
12
+---+------------+------+------------+
| V | ROW_NUMBER | RANK | DENSE_RANK |
+---+------------+------+------------+
| a |          1 |    1 |          1 |
| a |          2 |    1 |          1 |
| a |          3 |    1 |          1 |
| b |          4 |    4 |          2 |
| c |          5 |    5 |          3 |
| c |          6 |    5 |          3 |
| d |          7 |    7 |          4 |
| e |          8 |    8 |          5 |
+---+------------+------+------------+

(Смотрите также этот SQLFiddle )

Как DENSE_RANK () может помочь при написании SELECT DISTINCT

Несомненно, ROW_NUMBER() является наиболее полезной функцией ранжирования среди вышеперечисленных, особенно когда вам нужно эмулировать предложения LIMIT .. OFFSET, как в DB2, Oracle (11g или меньше), Sybase SQL Anywhere (до версии 12), SQL Server (2008 и меньше). Читайте здесь о том, как jOOQ эмулирует это предложение SQL на различных диалектах SQL.

Но использование ROW_NUMBER() имеет ROW_NUMBER() проблему при использовании вместе с DISTINCT или UNION . Это препятствует тому, чтобы база данных была в состоянии удалить дубликаты, потому что ROW_NUMBER всегда будет давать различные значения в пределах раздела. В приведенном выше примере дублированные значения для TV были добавлены специально. Как мы можем сначала удалить дубликаты и только потом перечислять номера строк? Ясно, что мы больше не можем использовать ROW_NUMBER() . Следующий запрос:

1
2
3
4
5
6
SELECT DISTINCT
  v,
  ROW_NUMBER() OVER (w) row_number
FROM t
WINDOW w AS (ORDER BY v)
ORDER BY v, row_number

… дает

01
02
03
04
05
06
07
08
09
10
11
12
+---+------------+
| V | ROW_NUMBER |
+---+------------+
| a |          1 |
| a |          2 |
| a |          3 |
| b |          4 |
| c |          5 |
| c |          6 |
| d |          7 |
| e |          8 |
+---+------------+

(Смотрите также этот SQLFiddle )

Но вместо этого мы можем использовать DENSE_RANK() ! С DENSE_RANK() ранжирование применяется таким образом, что дублирующиеся записи получают одинаковый ранг. И нет никаких пробелов между званиями. Следовательно:

1
2
3
4
5
6
SELECT DISTINCT
  v,
  DENSE_RANK() OVER (w) row_number
FROM t
WINDOW w AS (ORDER BY v)
ORDER BY v, row_number

… который дает:

1
2
3
4
5
6
7
8
9
+---+------------+
| V | ROW_NUMBER |
+---+------------+
| a |          1 |
| b |          2 |
| c |          3 |
| d |          4 |
| e |          5 |
+---+------------+

(Смотрите также этот SQLFiddle )

Таким образом, помните …

Таким образом, помните: ROW_NUMBER() должен SELECT то, что DENSE_RANK() должен SELECT DISTINCT

Предостережения

Однако для того, чтобы вышеприведенное было верным, необходимо убедиться, что все выражения из предложения SELECT DISTINCT используются в предложении DENSE_RANK() OVER(ORDER BY ...) . Например:

1
2
3
4
5
6
7
SELECT DISTINCT
  v1,
  v2,
  v3,
  DENSE_RANK() OVER (w) row_number
FROM t
WINDOW w AS (ORDER BY v1, v2, v3)

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