SQL-select top 3 values per group WITH CONDITION

2019-04-09 00:45发布

问题:

I want to pull out top 3 selling products for different product category per tag. Data looks like this:

tag  | product_name | product_category | order_count
tag1 | product1     | category1        | 100
tag1 | product2     | category2        | 80
tag1 | product3     | category2        | 60
tag1 | product4     | category3        | 50
......

I know how to pull out top 3 selling products per tag using ROW_NUMBER(), but it will return product1,product2,product3. I don't want product3 because it belongs to the same category as product2. I want product4 instead. How to do this in SQL server?

回答1:

First ROW_NUMBER removes duplicate rows per tag and product_category, second ROW_NUMBER selects top 3 selling products per tag

;WITH cte AS
 (SELECT *, ROW_NUMBER() OVER(PARTITION BY tag, product_category ORDER BY order_count DESC) AS rn
  FROM yourtable
  ), cte2 AS
  (SELECT *, ROW_NUMBER() OVER(PARTITION BY tag ORDER BY order_count DESC) AS rn2
   FROM cte
   WHERE rn = 1
   )
   SELECT *
   FROM cte2
   WHERE rn2 <= 3

Demo on SQLFiddle

Next one uses a derived table

;WITH cte AS
 (SELECT t2.tag, t2.product_name, t2.product_category, t2.order_count,
         ROW_NUMBER() OVER(PARTITION BY t2.tag ORDER BY order_count DESC) AS rn
  FROM (SELECT tag, product_category, MAX(order_count) AS maxCount
        FROM yourtable
        GROUP BY tag, product_category
        ) t1 JOIN yourtable t2 ON t1.tag = t2.tag 
          AND t1.product_category = t2.product_category
          AND maxCount = order_count
  )
  SELECT *
  FROM cte
  WHERE rn <= 3

Demo on SQLFiddle



回答2:

You can use RANK() (or ROW_NUMBER()) as long as you use PARTITION BY. This in combination with TOP() should work well assuming you're using SQL Server 2005+:

with cte as (
  select tag, 
    product_name, 
    product_category, 
    order_count, 
    rank() over (partition by product_category 
                 order by product_category, order_count desc) rnk
  from yourtable
  )
select top 3 tag, product_name, product_category, order_count
from cte 
where rnk = 1
order by order_count desc
  • SQL Fiddle Demo

This will produce the following:

TAG     PRODUCT_NAME   PRODUCT_CATEGORY  ORDER_COUNT
tag1    product1       category1         100
tag1    product2       category2         80
tag1    product4       category3         50


回答3:

I would suggest doing it with one select only

declare @t table (
    tag                 varchar(10),
    product_name        varchar(10),
    product_category    varchar(10),
    order_count         int
);

insert into @t values
('tag1', 'product1', 'category1', 100),
('tag1', 'product2', 'category2', 80 ),
('tag1', 'product3', 'category2', 60 ),
('tag1', 'product4', 'category3', 50 ),
('tag1', 'product5', 'category4', 40 );


select top 3
    *
from
    @t
order by
    row_number() over(partition by product_category order by order_count desc), 
    order_count desc;

SQL Fiddle