Multiple Ranks in one table

前端 未结 6 951
庸人自扰
庸人自扰 2020-11-27 22:00

I need the following, Can anyone please help me do it.

Rank   Cust_Type   Cust_Name   Revenue
1      Top         A           10000
2      Top         B               


        
相关标签:
6条回答
  • 2020-11-27 22:07

    This is a bit tricky. You may want to use variables, such as in the following example:

    SELECT    ( 
                CASE cust_type 
                WHEN @curType 
                THEN @curRow := @curRow + 1 
                ELSE @curRow := 1 AND @curType := cust_type END
              ) + 1 AS rank,
              cust_type,
              cust_name,
              revenue
    FROM      sales,
              (SELECT @curRow := 0, @curType := '') r
    ORDER BY  cust_type DESC, revenue DESC;
    

    The (SELECT @curRow := 0, @curType := '') r part allows the variable initialization without requiring a separate SET command.

    Test case:

    CREATE TABLE sales (cust_type varchar(10), cust_name varchar(10), revenue int);
    
    INSERT INTO sales VALUES ('Top', 'A', 10000);
    INSERT INTO sales VALUES ('Top', 'B', 9000);
    INSERT INTO sales VALUES ('Top', 'C', 8000);
    INSERT INTO sales VALUES ('Bottom', 'X', 5000);
    INSERT INTO sales VALUES ('Bottom', 'Y', 6000);
    INSERT INTO sales VALUES ('Bottom', 'Z', 7000);
    

    Result:

    +------+-----------+-----------+---------+
    | rank | cust_type | cust_name | revenue |
    +------+-----------+-----------+---------+
    |    1 | Top       | A         |   10000 |
    |    2 | Top       | B         |    9000 |
    |    3 | Top       | C         |    8000 |
    |    1 | Bottom    | Z         |    7000 |
    |    2 | Bottom    | Y         |    6000 |
    |    3 | Bottom    | X         |    5000 |
    +------+-----------+-----------+---------+
    6 rows in set (0.00 sec)
    

    Another test case:

    CREATE TABLE sales (cust_type varchar(10), cust_name varchar(10), revenue int);
    
    INSERT INTO sales VALUES ('Type X', 'A', 7000);
    INSERT INTO sales VALUES ('Type X', 'B', 8000);
    INSERT INTO sales VALUES ('Type Y', 'C', 5000);
    INSERT INTO sales VALUES ('Type Y', 'D', 6000);
    INSERT INTO sales VALUES ('Type Y', 'E', 4000);
    INSERT INTO sales VALUES ('Type Z', 'F', 4000);
    INSERT INTO sales VALUES ('Type Z', 'G', 3000);
    

    Result:

    +------+-----------+-----------+---------+
    | rank | cust_type | cust_name | revenue |
    +------+-----------+-----------+---------+
    |    1 | Type Z    | F         |    4000 |
    |    2 | Type Z    | G         |    3000 |
    |    1 | Type Y    | D         |    6000 |
    |    2 | Type Y    | C         |    5000 |
    |    3 | Type Y    | E         |    4000 |
    |    1 | Type X    | B         |    8000 |
    |    2 | Type X    | A         |    7000 |
    +------+-----------+-----------+---------+
    7 rows in set (0.00 sec)
    

    You can obviously order the cust_type in ascending order instead of descending. I used descending just to have Top before Bottom in the original test case.

    0 讨论(0)
  • 2020-11-27 22:08

    This is similar to Thomas's answer, but a little simpler:

    SELECT (SELECT COUNT(Cust_Type) FROM sales 
               WHERE Cust_Type = S.Cust_Type AND Revenue >= S.Revenue) AS Rank,
            Cust_Type,
            Cust_Name,
            Revenue 
      FROM sales AS S
      ORDER BY Cust_Type DESC, Rank;
    

    I'm not sure how the performance compares to the Daniel's solution, particularly on very large data sets, or if you have to use complex joins.

    0 讨论(0)
  • 2020-11-27 22:09

    What is not exactly clear is how the items should be ranked (I assumed by Revenue) or whether you are only pulling a certain number of values (e.g. the top 3 and the bottom 3) so I assumed you wanted all values. Given those assumptions,

    Select Cust_Name, Cust_Type
        , (Select Count(*)
            From Table As T1
            Where T1.Revenue > T.Revenue ) + 1 As Rank
    From Table As T
    Where Cust_Type = 'Top'
    Union All
    Select Cust_Name, Cust_Type
        , (Select Count(*)
            From Table As T1
            Where T1.Revenue < T.Revenue ) + 1 As Rank
    From Table As T
    Where Cust_Type = 'Bottom'
    

    If you were trying to do this in a single non-union query you could do:

    Select Cust_Name, Cust_Type
        , Case Z.Cust_Type
            When 'Top' Then Z.TopRank
            Else Z.BottomRank
            End As Rank
    From    (
            Select Cust_Name, Cust_Type
                , (Select Count(*)
                    From Table As T1
                    Where T1.Revenue > T.Revenue ) + 1 As TopRank
                , (Select Count(*)
                    From Table As T1
                    Where T1.Revenue < T.Revenue ) + 1 As BottomRank
            From Table As T
            ) As Z
    
    0 讨论(0)
  • 2020-11-27 22:12

    This works for me by keeping ranking of sales revenue and ordering separate .

    SELECT 
             (Select count(s1.revenue)+1 from sales s1 where s.cust_type_id = s1.cust_type_id and s1.revenue > s.revenue) 
              As rank,
    t.type_name,
              s.cust_name,
              s.revenue
    FROM      sales s LEFT JOIN 
              cust_type t USING(cust_type_id)
              Group by t.type_name,s.cust_name,s.revenue DESC order by s.revenue DESC;
    
    0 讨论(0)
  • 2020-11-27 22:21

    For JOIN tables problem, I found a solution.

    I create a temporary table, this way MySQL maintain the order of value that I want to rank.

    DROP TEMPORARY TABLE IF EXISTS tmp_mytable;
    
    CREATE TEMPORARY TABLE tmp_mytable ENGINE = MEMORY
        SELECT mytable.id AS id,
               mytable.login AS Login,
               cliente.myrank_id AS id_myrank,
               mytable.rankvalue AS rankvalue
        FROM mytable
       INNER JOIN myjoin ON (mytable.id_myjoin = myjoin.id)
        ORDER BY 3, 4 DESC;
    
    SELECT id, login, IFNULL(id_myrank, 0) AS id_myrank, rankvalue,
           @rank := IF(@prev_born = IFNULL(id_myrank, 0), @rank + 1, 1) AS ranking,
           @prev_Born := IFNULL(id_myrank, 0) AS fake_field
    FROM tmp_mytable, (select @prev_born := 0, @rank := 0) r
    -- HAVING ranking < 20;
    

    *PS: I tried with View creation, but insn't work too.

    0 讨论(0)
  • 2020-11-27 22:25

    I found a problem with the solution using CASE, @curRow, and @curType. It depends on the execution plan MySQL uses to process the query. For example, it shows up if you add a join to the query. Then there is no guarantee that the rank is going to be computed correctly.

    Making a slight change to the answer:

    CREATE TABLE sales (cust_type_id int, cust_name varchar(10), revenue int);
    CREATE TABLE cust_type (cust_type_id int, type_name varchar(10));
    
    INSERT INTO cust_type VALUES (1, 'Bottom');
    INSERT INTO cust_type VALUES (2, 'Top');
    
    INSERT INTO sales VALUES (2, 'A', 10000);
    INSERT INTO sales VALUES (2, 'B', 9000);
    INSERT INTO sales VALUES (2, 'C', 8000);
    INSERT INTO sales VALUES (1, 'X', 5000);
    INSERT INTO sales VALUES (1, 'Y', 6000);
    INSERT INTO sales VALUES (1, 'Z', 7000);
    

    If I query only the sales table I get the rank in the correct order, but if I join to the cust_type table the rank values are no longer correct

    SELECT    ( 
                CASE s.cust_type_id 
                WHEN @curType 
                THEN @curRow := @curRow + 1 
                ELSE @curRow := 1 AND @curType := s.cust_type_id END
              ) AS rank,
              t.type_name,
              s.cust_name,
              s.revenue
    FROM      sales s,
              cust_type t,
              (SELECT @curRow := 0, @curType := 0) r
    WHERE s.cust_type_id = t.cust_type_id 
    ORDER BY  t.type_name DESC, s.revenue DESC;
    

    Result:

    +------+-----------+-----------+---------+
    | rank | type_name | cust_name | revenue |
    +------+-----------+-----------+---------+
    |    1 | Top       | A         |   10000 |
    |    2 | Top       | B         |    9000 |
    |    3 | Top       | C         |    8000 |
    |    3 | Bottom    | Z         |    7000 |
    |    2 | Bottom    | Y         |    6000 |
    |    1 | Bottom    | X         |    5000 |
    +------+-----------+-----------+---------+
    

    MySQL is running the initial query into a temp table and then the ORDER BY is executing against the temp table after a rank was already computed.

    0 讨论(0)
提交回复
热议问题