在多列中选择价值最低的行,但不包含ROW_NUMBER

时间:2019-02-12 18:00:19

标签: sql sql-server tsql sql-server-2012 greatest-n-per-group

我想获得每组的行以及两列的最小值。

我有一张桌子,上面有我想要的物品的清单,以及它们的成本和与我的距离。

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 :(得分:1)

关于这个主题的一篇不错的文章是Itzik Ben Gan-Optimizing TOP N Per Group Queries。本文讨论了串联方法。

例如,如果您的桌子是

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的排序将具有相同的顺序,因此如果您的数据类型不同,上述查询可能需要进行调整

当前,它为cost保留最左边的10个字符,用整数便士表示,并用前导零值填充,而dist则类似地作为10位整数。

答案 1 :(得分:0)

您可以这样做

; 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;

但是,建议您将索引添加到itemcost列中,以加快查询速度。

[编辑] 重新阅读OP问题后,如果存在成本联系,应该像以下内容,

; 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;

也许有更好的方法,但这应该可行。

答案 2 :(得分:0)

如果您有一个项目表,那么这可能会起作用:

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)上建立索引。

答案 3 :(得分:0)

类似的事情应该起作用:

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