2009-08-20 1 views
4

Этот вопрос возникает из обсуждения вопроса о том, использовать ли функцию ранжирования SQL или нет в particular case.Если функциональность ранжирования SQL следует рассматривать как «использовать с осторожностью»

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

Они отлично справляются с увеличением производительности, если вы хотите представить только небольшой кусок из огромного количества записей. Но они также вводят серьезную ошибку: если 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 вы найдете множество решений, предлагающих использовать функции ранжирования и пропустить, чтобы указать на возможные проблемы.

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

ответ

0

Это резюме:

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

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

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

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

select top 2 * from tblA order by date desc 

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

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

+0

+1. Отличный совет. – karlgrz

1

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

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

+0

Хорошо взревел лев. Первоначально я думал о том, чтобы сначала «использовать вашу голову» в списке соображений. Но что, если вы неопытный программист, задающий вопрос о SO, и кто-то предлагает решение TOP .. GROUP BY, не указав на это опасности? Вы можете попасть в неприятности, даже не осознавая ... –

+0

@Mao Tsetung: Это характер работы. Ничто не является таким простым или очевидным. Вы должны учиться, делать ошибки, сожжены, находить обходные пути и тем самым накапливать знания и опыт. Нет ярлыков. – 2009-08-20 11:55:33

2

Use the WITH TIES clause in your example above

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 отличается)

+0

+1, так как я не знал о том, что «TOP n WITH TIES ... ORDER BY key». Это еще одна альтернатива. Как и следовало ожидать, я не согласен с вами в вопросе даты - даты. Я не хочу «очень низкого риска». Я хочу «без риска». Да, я знаю ... Никакого риска, нет веселья ... –

2

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

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

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

Вы не имеете непосредственный доступ к следующим:

  • указатель строки в MyISAM
  • Uniquifier в InnoDB таблице без PRIMARY KEY определяется
  • RID в кучи таблицы SQL Server «s
  • Ключ индекса + uniquifier в таблице SQL Server ред на неоднозначный ключ

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

  • ROWID/UROWID в Oracle
  • CTID в PostgreSQL.

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

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

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

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

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

SELECT * 
FROM mytable 
ORDER BY 
     ordering_column, pk 

В противном случае, включают всех столбцов в состояние заказа:

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

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

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

Но не полагайтесь на ROWID/CTID, чтобы заказать строки.

Его можно легко изменить на UPDATE, чтобы ваш результат не был стабильным.

+0

Очень подробный вид на ключ-ключ-уникальный совет. Благодаря! –

Смежные вопросы