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

#sql-server #tsql

#sql-server #tsql

Вопрос:

Используя T-SQL, как я могу выбрать n строк неключевого, неиндексного столбца и избежать дублирования результатов?

Пример таблицы:

 ID_ | state    | customer | memo
------------------------------------------
1   |  abc     | 123      | memo text xyz
2   |  abc     | 123      | memo text abc
3   |  abc     | 456      | memo text def
4   |  abc     | 456      | memo text rew
5   |  abc     | 789      | memo text yte
6   |  def     | 123      | memo text hrd
7   |  def     | 432      | memo text dfg
  

Я хочу выбрать, скажем, 2 заметки для состояния «abc», но возвращаемые заметки не должны быть для одного и того же клиента.

 memo
----
memo text xyz
memo text def
  

PS: единственное доступное условие выбора — это состояние (например: где state = ‘abc’)

Мне удалось сделать это очень неэффективным способом

 SELECT top 2 MAX(memo)
FROM table
WHERE state = 'abc'
GROUP BY customer
  

Это отлично работает для небольшого размера выборки, но рабочая таблица содержит более 1 миллиарда строк.

Комментарии:

1. i hv doubt…do вам нужен результат для клиента 789 или нет…

2. В примере, который я упомянул, ответ отрицательный, поскольку N в этом случае равно 2. Если бы это было 3, то да, результат включал бы значение 789.

Ответ №1:

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

 SELECT memo
FROM   (SELECT memo,
               ROW_NUMBER() OVER (PARTITION BY customer ORDER BY (SELECT 0)) AS RN
        FROM   table1 WHERE state = 'abc') T
WHERE  RN = 1 
  

Вы можете проверить СКРИПКУ SQL

РЕДАКТИРОВАТЬ: добавление некластеризованного индекса для состояния и клиента, включая memo, значительно повысит производительность.

 CREATE NONCLUSTERED INDEX [custom_index] ON table 
(
    [state] ASC,
    [customer] ASC
)
INCLUDE ( [memo]) WITH (SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [DATA]
  

Комментарии:

1. Спасибо. Мне пришлось изменить ваш запрос, как показано ниже, и добавить некластеризованный индекс для состояния и клиента. ВЫБЕРИТЕ верхнюю заметку 2 ИЗ (ВЫБЕРИТЕ customer, state, memo, ROW_NUMBER() ПОВЕРХ (РАЗДЕЛЕНИЕ ПО ЗАКАЗУ клиента ПО (ВЫБЕРИТЕ 0)) КАК RN ИЗ table1, ГДЕ state = ‘abc’) T ГДЕ RN = 1

2. Будет ждать альтернативных предложений, прежде чем пометить это как ответ.

3. Да, определенно, я тоже хочу знать, есть ли лучшее решение 🙂 @Jayachandran . Также вы можете исключить customer и state из инструкции select подзапроса.

4. Да, только что заметил, что 🙂

5. PS: Проголосую за ответ, как только у меня будет требуемая репутация.

Ответ №2:

Способ получить это n различных значений для состояния / клиента — получить идентификатор для каждой группы

 SELECT MIN(ID_) ID
FROM   Table1
GROUP BY State, customer
  

( MIN может быть заменено на MAX , это просто способ получить одно из значений)
затем JOIN добавьте это в таблицу, добавив другое условие

 WITH getID AS (
  SELECT MIN(ID_) ID
  FROM   Table1
  GROUP BY State, customer
)
SELECT TOP 2
       t.ID_, t.State, t.Customer, t.memo
FROM   table1 t
       INNER JOIN getID g ON t.ID_ = g.ID
WHERE  t.state = 'abc'
  

SQLFiddle demo

если ваша версия SQLServer не поддерживает WITH CTE , может стать подзапросом

 SELECT TOP 2
       t.ID_, t.State, t.Customer, t.memo
FROM   table1 t
       INNER JOIN (SELECT MIN(ID_) ID
                   FROM   Table1
                   GROUP BY State, customer
                  ) g ON t.ID_ = g.ID
WHERE  t.state = 'abc'
  

Другой способ — использовать CROSS APPLY для получения ID

 SELECT TOP 2
       t.ID_, t.State, t.Customer, t.memo
FROM   table1 t
       CROSS APPLY (SELECT TOP 1
                           ID_
                    FROM   table1 t1
                    WHERE  t1.State = t.State AND t1.Customer = t.Customer) c
WHERE  t.state = 'abc'
  AND  c.ID_ = t.ID_;
  

SQLFiddle demo

Комментарии:

1. Спасибо за это предложение. Рад сообщить, что этот метод также работает и дает ожидаемый результат. Однако, когда я выполнил оба предложения @TechGirl и ваше предложение (как CTE, так и подзапрос) параллельно, относительное разделение затрат составило 40: 60. Поэтому я склонен использовать метод номера строки.

2. :-). Я не имел в виду это как оскорбление. Я просто сообщал о своем результате для других, которые могли бы найти это полезным, и им не придется заново изобретать колесо. Как упоминалось в вопросе, моя самая большая проблема — производительность. Как я уже сказал, ваше решение работает отлично.

3. Относительная стоимость — это то, что я проверил на лету. Я все еще изучаю варианты. Одна вещь, которую я заметил, заключалась в том, что в вашем запросе было соединение с результатом поиска по индексу (memo) и сканирования индекса (для идентификатора, состояния и клиента), где, как у @TechGirl, был поиск по индексу (для customer и memo) и фильтр. Возможно, есть способ избежать проверки индекса, используя покрывающий индекс. Но тогда это занимает много места:(.

4. Работает, но ухудшает производительность. Сканирование индекса исключается и заменяется поиском по индексу, однако оно извлекает 2 миллиона записей по сравнению с всего лишь 25 сканированием индекса :-).

5. PS: Проголосую за ответ, как только у меня будет требуемая репутация.