Должна ли функциональность ранжирования SQL рассматриваться как «использовать с осторожностью» - PullRequest
4 голосов
/ 20 августа 2009

Этот вопрос возникает из дискуссии о том, использовать ли функцию ранжирования SQL или нет в конкретном случае .

Любая распространенная СУБД включает в себя некоторые функции ранжирования, то есть ее язык запросов имеет такие элементы, как TOP n ... ORDER BY key, ROW_NUMBER() OVER (ORDER BY key) или ORDER BY key LIMIT n ( обзор ).

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


users

user_id name
1       John
2       Paul
3       George
4       Ringo

logins

login_id user_id login_date
1        4       2009-08-17
2        1       2009-08-18
3        2       2009-08-19
4        3       2009-08-20

Предполагается, что запрос вернет человека, который вошел последним:

SELECT TOP 1 users.*
FROM
  logins JOIN
  users ON logins.user_id = users.user_id
ORDER BY logins.login_date DESC

Как и ожидалось, возвращается George, и все выглядит хорошо. Но затем в таблицу logins вставляется новая запись:

1        4       2009-08-17
2        1       2009-08-18
3        2       2009-08-19
4        3       2009-08-20
5        4       2009-08-20

Что теперь возвращает запрос выше? Ringo? George? Ты не можешь сказать. Насколько я помню например MySQL 4.1 возвращает первую физически созданную запись, которая соответствует критериям, то есть результат будет George. Но это может варьироваться от версии к версии и от СУБД к СУБД. Что должно было быть возвращено? Кто-то может сказать Ringo, поскольку он, по-видимому, вошел последним, но это чистая интерпретация По моему мнению, оба должны были быть возвращены, потому что вы не можете однозначно определиться с доступными данными.

Таким образом, этот запрос соответствует требованиям:

SELECT users.*
FROM
  logins JOIN
  users ON
    logins.user_id = users.user_id AND
    logins.login_date = (
      SELECT max(logins.login_date)
      FROM
        logins JOIN
        users ON logins.user_id = users.user_id)

В качестве альтернативы некоторые СУБД предоставляют специальные функции (например, Microsoft SQL Server 2005 вводит TOP n WITH TIES ... ORDER BY key (предлагается gbn ), RANK и DENSE_RANK для этой самой цели).


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

Вопрос: Какой совет следует дать, если предлагается решение, включающее функции ранжирования?

Ответы [ 5 ]

3 голосов
/ 20 августа 2009

rank и row_number - фантастические функции, которые следует использовать более свободно, ИМО. Люди просто не знают о них.

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

Я думаю, что подводные камни здесь одинаковы в запросе:

select top 2 * from tblA order by date desc

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

Кроме того, для этой записи SQL Server не сохраняет строки в физическом порядке, в котором они вставлены. Он хранит записи на 8 тыс. Страниц и упорядочивает эти страницы наиболее эффективным способом в соответствии с кластерным индексом в таблице. Таким образом, нет абсолютно никаких гарантий порядка в SQL Server.

2 голосов
/ 20 августа 2009

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

Эти идентификаторы:

  • Указатель строки в MyISAM
  • Первичный ключ в таблице InnoDB с определением PRIMARY KEY
  • Uniquifier в таблице InnoDB без определения PRIMARY KEY
  • RID в SQL Server 'таблица кучи
  • Первичный ключ в таблице SQL Server, кластеризованной на PRIMARY/UNIQUE KEY
  • Индексный ключ + uniquifier в таблице SQL Server, кластеризованной на неуникальном ключе
  • ROWID / UROWID в Oracle
  • CTID в PostgreSQL.

У вас нет немедленного доступа к следующимиз них:

  • Указатель строки в MyISAM
  • Uniquifier в InnoDB таблице без PRIMARY KEY определенного
  • RID в SQL ServerТаблица кучи
  • Индексный ключ + uniquifier в таблице SQL Server, кластеризованной на неуникальном ключе

Кроме того, вы не можете контролировать следующееиз них:

  • ROWID / UROWID in Oracle
  • CTID in PostgreSQL.

(они могут изменяться при обновлении или восстановлении из резервных копий)

Если в этих таблицах две строки идентичны, это означает, что они должны быть идентичны с точки зрения приложения.

Они возвращают точно такие же результаты и могут рассматриваться как окончательные уникальные значения.

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

Если в вашей таблице есть первичный или уникальный ключ (даже составной), включите его в условие заказа:

SELECT  *
FROM    mytable
ORDER BY
        ordering_column, pk

В противном случае включите all столбцы в условии упорядочения:

SELECT  *
FROM    mytable
ORDER BY
        ordering_column, column1, ..., columnN

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

Это, кстати, еще одна веская причина всегда иметь PRIMARY KEY в своих таблицах.

Но неТолько для ROWID / CTID для упорядочивания строк.

Может легко измениться на UPDATE, поэтому ваш порядок результатов не будет более стабильным.

2 голосов
/ 20 августа 2009

Используйте предложение WITH TIES в приведенном выше примере

SELECT TOP 1 WITH TIES users.*
FROM
  logins JOIN
  users ON logins.user_id = users.user_id
ORDER BY logins.login_date DESC

Используйте DENSE_RANK, как вы упомянули

Не ставьте себя в это положение Пример: сохраняйте время (datetime) и принимайте очень низкий риск очень редкого дубликата в один и тот же момент времени 3,33 миллисекунды (отличается SQL 2008)

1 голос
/ 20 августа 2009

ROW_NUMBER действительно фантастический инструмент. В случае неправильного использования он может дать недетерминированные результаты, как и другие функции SQL. Вы также можете ORDER BY возвращать недетерминированные результаты.

Просто знай, что ты делаешь.

0 голосов
/ 25 октября 2009

Это резюме:

  • Сначала используйте свою голову. Должно быть очевидно, но это всегда хороший момент для начала. Вы ожидаете n строк точно или ожидаете, возможно, различного количества строк, которые удовлетворяют ограничению? Пересмотрите свой дизайн. Если вы ожидаете ровно 10000 строк, ваша модель может быть плохо спроектирована, если невозможно однозначно определить строку. Если вы ожидаете возможного варьирования количества строк, вам может потребоваться настроить ваш пользовательский интерфейс для представления результатов запроса.
  • Добавьте столбцы к key, которые делают его уникальным (например, PK). Вы по крайней мере получаете контроль над возвращенным результатом. Почти всегда есть способ сделать это, как указал Кассной .
  • Попробуйте использовать более подходящие функции, такие как RANK, DENSE_RANK и TOP n WITH TIES. Они доступны в Microsoft SQL Server версии 2005 и в PosgreSQL начиная с 8.4. Если эти функции недоступны, рассмотрите возможность использования вложенных запросов с агрегацией вместо функций ранжирования.
...