Выберите строку с наименьшим значением в нескольких столбцах без ROW_NUMBER - PullRequest
0 голосов
/ 12 февраля 2019

Я хочу получить строку для группы с минимальным значением в два столбца.

У меня есть таблица, в которой есть списки нужных мне предметов, а также их стоимость и расстояние до меня.

mytable:
item | cost | dist
-----+------+---------
1    | $2   | 1.0
1    | $3   | 0.5
1    | $4   | 2.0
2    | $2   | 2.0
2    | $2   | 1.5
2    | $2   | 4.0
2    | $8   | 1.0
2    | $12  | 3.0
3    | $1   | 5.0

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

, чтобы мой результат был

item | cost | dist
-----+------+---------
1    | $2   | 1.0
2    | $2   | 1.5
3    | $1   | 5.0

Я знаю, что могу достичь этого результата, используя

SELECT * 
, ROW_NUMBER() OVER(PARTITION BY item ORDER BY cost ASC, dist ASC) as [RID]
FROM mytable
WHERE [RID] = 1

, но проблема возникает, когда у меня 100 000 элементов каждый с 100 000 списков, и сортировка всей таблицы становится невероятно трудоемкой.

Поскольку мне нужны только первые 1 в каждой группе, мне интересно, есть ли другой способ получить желаемый результат без сортировки всей таблицы из 10 000 000 000 записей.

В настоящее время используетсяSQL Server 2012

Ответы [ 4 ]

0 голосов
/ 12 февраля 2019

Хорошая статья на эту тему - Ицик Бен Ган - Оптимизация TOP N для групповых запросов .Здесь обсуждается подход конкатенации.

Например, если ваша таблица

CREATE TABLE #YourTable
  (
     item INT,
     cost MONEY CHECK (cost >= 0),
     dist DECIMAL(10, 2) CHECK (dist >= 0)
  ) 

, вы можете использовать

WITH T AS
(
SELECT item,  
       MIN(FORMAT(CAST(cost * 100 AS INT), 'D10') + FORMAT(CAST(dist * 100 AS INT), 'D10')) AS MinConcat
FROM #YourTable
GROUP BY item
)
SELECT item,
       CAST(LEFT(MinConcat,10)/100.0 AS MONEY),
       CAST(RIGHT(MinConcat,10)/100.0 AS  DECIMAL(10,2))
FROM T

Так что это можно сделать в одной операции группировки наid (который может быть агрегатом хэшей без какой-либо сортировки).

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

В настоящее время он резервирует самые левые 10 символов для cost, представленного в виде целого числа пенсов и дополненного лидирующими нулями, и dist как 10-значное целое число аналогично.

0 голосов
/ 12 февраля 2019

Если у вас есть таблица предметов, это может сработать:

select i.*, t.*
from items i cross apply
     (select top (1) t.*
      from t
      where t.item = i.item
      order by cost, dist
     ) t;

Чтобы это было эффективно, вам нужен индекс на (item, cost, dist).

0 голосов
/ 12 февраля 2019

Примерно так должно работать:

select
    t.item, MIN(t.cost) as mincost, min(t2.mindist) as mindist
from mytable t
inner join (
select item, cost, MIN(dist) as mindist
    from mytable
    group by
        item, cost
) t2 on t.item = t2.item
group by t.item,t2.cost
having MIN(t.cost) = t2.cost
0 голосов
/ 12 февраля 2019

Вы можете сделать это следующим образом

; with c as 
(select min(cost) as cost, item
from mytable
group by item)
select t.* from mytable t
inner join c
on c.item = t.item and c.cost=t.cost;

Однако я бы порекомендовал вам добавить индекс к столбцам item и cost, чтобы сделать запрос быстрым.

[Редактировать] После перечитывания вопроса об операторе это должно быть похоже на следующее, когда есть связи в стоимости:

; with c as 
(select min(cost) as cost, item
from mytable
group by item)
, c2 as (
select t.cost, t.item, min(dist) as dist from mytable t
inner join c
on c.item = t.item and c.cost=t.cost
group by t.cost, t.item)
select  t.item,t.cost, c2.dist from mytable t
inner join c2
on c2.item = t.item, and c2.cost = t.cost;

Может быть, есть лучшие способы, но это должно работать.

...