Функция SQL Rank (), исключающая строки

#sql #sql-server #window-functions

#sql #sql-сервер #окно-функции

Вопрос:

Рассмотрим, что у меня есть следующая таблица.

 ID  value
1   100
2   200
3   200
5   250
6   1
  

У меня есть следующий запрос, который дает следующий результат. Я хочу исключить значение 200 из функции rank, но все равно эта строка должна быть возвращена.

 SELECT
CASE WHEN Value = 200 THEN 0
     ELSE DENSE_RANK() OVER ( ORDER BY VALUE DESC)
END AS RANK,
ID,
VALUE
FROM @table

RANK   ID   VALUE
1       5   250
0       2   200
0       3   200
4       1   100
5       6   1
  

Но я хочу получить следующий результат. Как этого добиться?

 RANK   ID   VALUE
1       5   250
0       2   200
0       3   200
2       1   100
3       6   1
  

Ответ №1:

Если столбец VAL не имеет значения null, то с учетом NULL последнее значение в ORDER BY .. DESC

 select  *, dense_rank() over (order by nullif(val,200) desc) * case val when 200 then 0 else 1 end
from myTable
order by val desc;
  

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

1. Отличное решение

2. Отличный запрос! Хорошая работа

Ответ №2:

В настоящее время невозможно исключить значение Val в плотном ранге, если вы не фильтруете в предложении where .. по этой причине вы получаете результат ниже

 RANK   ID   VALUE
1       5   250
0       2   200
0       3   200
4       1   100
5       6   1
  

Вам нужно будет отфильтровать один раз, а затем выполнить объединение всех

 ;with cte(id,val)
as
(
select 1,   100  union all
select 2,   200 union all
select 3,   200 union all
select 5,   250 union all
select 6,   1    )
select  *, dense_rank() over (order by val desc) 
 from cte
 where val<>200
union all
 select 0,id,val from cte where val=200
  

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

1. При этом не будет извлечена строка со значением 200. Но я хочу, чтобы эта строка была включена.

Ответ №3:

Вы можете разделить рейтинг на отдельные запросы для значений, которые вы хотите включить / исключить из рейтинга, и UNION ALL результатов, подобных этому:

Пример автономного исполняемого файла:

 CREATE TABLE #temp ( [ID] INT, [value] INT );

INSERT  INTO #temp
        ( [ID], [value] )
VALUES  ( 1, 100 ),
        ( 2, 200 ),
        ( 3, 200 ),
        ( 5, 250 ),
        ( 6, 1 );

SELECT  *
FROM    ( SELECT    0 RANK ,
                    ID ,
                    value
          FROM      #temp
          WHERE     value = 200  -- set rank to 0 for value = 200
          UNION ALL
          SELECT    DENSE_RANK() OVER ( ORDER BY value DESC ) AS RANK ,
                    ID ,
                    value
          FROM      #temp
          WHERE     value != 200  -- perform ranking on records != 200
        ) t
ORDER BY value DESC ,
        t.ID

DROP TABLE #temp
  

Выдает:

 RANK    ID  value
1       5   250
0       2   200
0       3   200
2       1   100
3       6   1
  

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

Ответ №4:

Вы также можете попробовать это тоже:

 SELECT  ISNULL(R, 0) AS Rank ,t.id ,t.value
FROM    tbl1 AS t
    LEFT JOIN ( SELECT  id ,DENSE_RANK() OVER ( ORDER BY value DESC ) AS R
                FROM    dbo.tbl1 WHERE   value <> 200
              ) AS K 
   ON t.id = K.id
ORDER BY t.value DESC
  

Ответ №5:

Решение в исходном вопросе было на самом деле довольно близким. Простое добавление предложения partition к dense_rank может помочь.

 SELECT    CASE
               WHEN VALUE = 200 THEN 0
               ELSE DENSE_RANK() OVER(
                    PARTITION BY CASE WHEN VALUE = 200 THEN 0 ELSE 1 END 
                    ORDER BY VALUE DESC
               )
          END AS RANK
          ,ID
          ,VALUE
FROM      @table
ORDER BY  VALUE DESC;
  

‘Partition by’ создает отдельные группы для dense_rank таким образом, что порядок выполняется для этих групп по отдельности. По сути, это означает, что вы создаете два ранга одновременно: один для группы без значения 200 и один для группы только со значением 200. Последний должен быть установлен в 0 в «случае, когда».

Пример автономного исполняемого файла:

 DECLARE @table TABLE
(
     ID     INT NOT NULL PRIMARY KEY
     ,VALUE INT     NULL
)

INSERT INTO @table
(
     ID
     ,VALUE
)
SELECT 1, 100
UNION SELECT 2, 200
UNION SELECT 3, 200
UNION SELECT 5, 250
UNION SELECT 6, 1;

SELECT    CASE
               WHEN VALUE = 200 THEN 0
               ELSE DENSE_RANK() OVER(
                    PARTITION BY CASE WHEN VALUE = 200 THEN 0 ELSE 1 END 
                    ORDER BY VALUE DESC
               )
          END AS RANK
          ,ID
          ,VALUE
FROM      @table
ORDER BY  VALUE DESC;
  
 RANK    ID  VALUE
1   5   250
0   2   200
0   3   200
2   1   100
3   6   1