Функция SQL Row_Number () в предложении Where

91

Я нашел один вопрос, на который отвечает Row_Number()функция в предложении where. Когда я попробовал один запрос, я получил следующую ошибку:

"Msg 4108, уровень 15, состояние 1, строка 1 Оконные функции могут появляться только в предложениях SELECT или ORDER BY."

Вот запрос, который я пробовал. Если кто-нибудь знает, как это решить, дайте мне знать.

SELECT employee_id 
FROM V_EMPLOYEE 
WHERE row_number() OVER ( ORDER BY employee_id ) > 0 
ORDER BY Employee_ID
Джонни Роуз
источник
10
ROW_NUMBER() OVER (ORDER BY employee_id) > 0всегда буду оцениватьTRUE
Quassnoi
3
Да, верно. Меня не беспокоит состояние, которое я могу изменить в любой момент. Я хочу, чтобы запрос работал сначала, а затем подумал о том, чтобы сохранить номер строки между 500 и 800 ... спасибо
2
@ Джозеф: Почему вы пытаетесь избегать использования CTE?
OMG Ponies,
1
@rexem - я не специалист по SQL Server. Я пытаюсь помочь команде в большом проекте, где у них много проблем с производительностью. Они используют UDF и CTE. В одной из таблиц у них всего 5000 записей, и если 5 пользователей обращаются к поисковому запросу, на поиск уходит больше минуты. Некоторое время он терпит неудачу и время ожидания. Итак, я пытаюсь избегать CTE и UDF и пытаюсь создать простой SQL-запрос, который может решить проблемы с производительностью.
1
Привет всем, пожалуйста, посмотрите ссылку, которую я разместил ниже, которая отвечает с использованием row_number () другим способом. Может ли кто-нибудь сравнить мой первоначальный запрос с тем, который указан в ссылке? Цените помощь ..

Ответы:

96

Чтобы обойти эту проблему, оберните оператор select в CTE, а затем вы можете запросить CTE и использовать результаты оконной функции в предложении where.

WITH MyCte AS 
(
    select   employee_id,
             RowNum = row_number() OVER ( order by employee_id )
    from     V_EMPLOYEE 
    ORDER BY Employee_ID
)
SELECT  employee_id
FROM    MyCte
WHERE   RowNum > 0
Скотт Айви
источник
7
Я пытаюсь избежать CTE. Я ищу худший случай. спасибо
3
Он может работать быстрее, если вы используете подзапрос вместо CTE. В некоторых случаях я видел лучшую производительность в 1,5 раза
Брайан Вебстер
3
В CTE SELECT также должен быть TOP, иначе SQL 2008 Server не выполнит запрос из-за ORDER BY (который не поддерживается, если не используется TOP)
Muflix
2
Я использую SQL2005 (тьфу) - я могу избежать использования «TOP», отбросив «ORDER BY» после FROM. В любом случае, это избыточно с (Order By) после OVER.
Joe B
Я пожалел , что есть способ использовать ROW_NUMBER()в WHEREпредложении без КТРА :(
Джалал
62
SELECT  employee_id
FROM    (
        SELECT  employee_id, ROW_NUMBER() OVER (ORDER BY employee_id) AS rn
        FROM    V_EMPLOYEE
        ) q
WHERE   rn > 0
ORDER BY
        Employee_ID

Обратите внимание, что этот фильтр является избыточным: ROW_NUMBER()начинается с 1и всегда больше чем 0.

Quassnoi
источник
2
@ DavideChicco.it: в SQL Server для производных таблиц требуется псевдоним (я должен был написать AS qвместо него, но это тоже сработает).
Quassnoi
2
При именовании псевдонимов я уделяю особое внимание читаемости. Вы можете написать rn как RowNumber и q как DerivedTable, а предложение where как where DerivedTable.RowNumber> 0. На мой взгляд, это будет намного менее запутанным через 6 месяцев, когда код еще не свеж в вашей памяти.
Эдвард Комо
2
@EdwardComeau: в наши дни rnявляется общепринятым аббревиатурой для номера строки. Попробуйте ввести "row_number over as ..." в строку поиска Google и посмотрите, что вам предложат.
Quassnoi 05
3
@Quassnoi, удобочитаемость является ключом к хорошему кодированию, и когнитивные усилия по переводу rn (или других сокращенных псевдонимов) складываются для вас и людей, поддерживающих ваш код. NB, Microsoft первый хит, SELECT ROW_NUMBER () OVER (ORDER BY SalesYTD DESC) AS Row, ... Я также не встречал rn раньше, поэтому ваш пробег в "универсальном" может отличаться.
Эдвард Комо
1
@Quassnoi, и второй удар, статья SO - stackoverflow.com/questions/961007/how-do-i-use-row-number несколько вариантов, а не rn ;-)
Эдвард Комо
32
Select * from 
(
    Select ROW_NUMBER() OVER ( order by Id) as 'Row_Number', * 
    from tbl_Contact_Us
) as tbl
Where tbl.Row_Number = 5
swa
источник
19

Я думаю, вам нужно что-то вроде этого:

SELECT employee_id 
FROM  (SELECT employee_id, row_number() 
       OVER (order by employee_id) AS 'rownumber' 
       FROM V_EMPLOYEE) TableExpressionsMustHaveAnAliasForDumbReasons
WHERE rownumber > 0
Мэтью Джонс
источник
4
Создайте псевдоним для таблицы, если указанный выше запрос вам не подходит. Измените вторую последнюю строку From V_EMPLOYEE) A, добавив в качестве псевдонима A.
Хаммад Хан
7

В ответ на комментарии к ответу rexem относительно того, будет ли встроенное представление или CTE быстрее, я переделал запросы, чтобы использовать таблицу, которая была доступна у меня и у всех: sys.objects.

WITH object_rows AS (
    SELECT object_id, 
        ROW_NUMBER() OVER ( ORDER BY object_id) RN
    FROM sys.objects)
SELECT object_id
FROM object_rows
WHERE RN > 1

SELECT object_id
FROM (SELECT object_id, 
        ROW_NUMBER() OVER ( ORDER BY object_id) RN
    FROM sys.objects) T
WHERE RN > 1

Созданные планы запросов были точно такими же. Я ожидал, что во всех случаях оптимизатор запросов предложит тот же план, по крайней мере, при простой замене CTE на встроенное представление или наоборот.

Конечно, попробуйте свои собственные запросы в своей системе, чтобы увидеть, есть ли разница.

Кроме того, row_number()в предложении where встречается распространенная ошибка в ответах на Stack Overflow. Логика row_number()недоступна, пока не будет обработано предложение select. Люди забывают об этом, и когда они отвечают, не проверив ответ, иногда ответ оказывается неверным. (Обвинение, в котором я сам был виновен.)

Шеннон Северанс
источник
1
Спасибо, Шеннон. Какую версию SQL Server вы использовали?
OMG Ponies,
1
Значит, ответ в этой ссылке неверен? Но человек, разместивший вопрос, согласился, что это работает .. Удивительно .. :-)
2
@Joseph, но если вы посмотрите на другой ответ, отправленный OP в связанном вопросе, вы увидите, что он ссылается на версию кода, которая не такая же, как в принятом ответе. Я не знаю, почему он принял ответ, хотя он не прошел так, как был введен. Возможно, он был отредактирован в какой-то момент после того, как был принят, может быть, этого было достаточно, чтобы он начал работать, даже не будучи полностью правильным.
Шеннон Северанс,
1
@Rexem: SQL Server 2005 и SQL Server 2008. Более ранние версии не поддерживают CTE или ROW_NUMBER ()
Шеннон Северанс,
6

Мне кажется, что все ответы, показывающие использование CTE или Sub Query, являются достаточными исправлениями для этого, но я не вижу, чтобы кто-то понял, почему у OP проблема. Причина, по которой предложенный OP не работает, связана с логическим порядком обработки запросов здесь:

  1. ИЗ
  2. НА
  3. ПРИСОЕДИНИТЬСЯ
  4. ГДЕ
  5. ГРУППА ПО
  6. С CUBE / ROLLUP
  7. ИМЕЕТ
  8. ВЫБРАТЬ
  9. ОТЛИЧИТЕЛЬНЫЙ
  10. СОРТИРОВАТЬ ПО
  11. ВЕРХНЯЯ
  12. Смещение / выборка

Я считаю, что это в значительной степени способствует ответу, поскольку объясняет, почему возникают подобные проблемы. WHEREвсегда обрабатывается до SELECTсоздания CTE или подзапроса, необходимого для многих функций. Вы часто будете видеть это в SQL Server.

Джейми Маршалл
источник
4

Использование CTE (SQL Server 2005+):

WITH employee_rows AS (
  SELECT t.employee_id,
         ROW_NUMBER() OVER ( ORDER BY t.employee_id ) 'rownum'
    FROM V_EMPLOYEE t)
SELECT er.employee_id
  FROM employee_rows er
 WHERE er.rownum > 1

Использование встроенного представления / альтернативы, не эквивалентной CTE:

SELECT er.employee_id
  FROM (SELECT t.employee_id,
               ROW_NUMBER() OVER ( ORDER BY t.employee_id ) 'rownum'
          FROM V_EMPLOYEE t) er
 WHERE er.rownum > 1
OMG Пони
источник
1
Какой лучше по производительности? Используете CTE или подзапрос? спасибо
1
См. Ответ Шеннона - в его тесте они равны.
OMG Ponies,
6
Нет, не быстрее. In SQL Server, CTEи встроенные представления - это одно и то же и имеют одинаковую производительность. Когда в a используются недетерминированные функции, они CTEпереоцениваются при каждом вызове. Чтобы принудительно материализовать файл, нужно использовать грязные уловки CTE. Смотрите эти статьи в моем блоге: explainextended.com/2009/07/28/... explainextended.com/2009/05/28/generating-xml-in-subqueries
Quassnoi
2

на основе ответа OP на вопрос:

См. Эту ссылку. У него другое решение, которое выглядит работающим для человека, задавшего вопрос. Я пытаюсь найти такое решение.

Запрос с разбивкой на страницы с использованием сортировки по разным столбцам с использованием ROW_NUMBER () OVER () в SQL Server 2005

~ Джозеф

«метод 1» похож на запрос OP из связанного вопроса, а «метод 2» похож на запрос из выбранного ответа. Вам нужно было посмотреть на код, связанный в этом ответе, чтобы увидеть, что на самом деле происходит, поскольку код в выбранном ответе был изменен, чтобы заставить его работать. Попробуй это:

DECLARE @YourTable table (RowID int not null primary key identity, Value1 int, Value2 int, value3 int)
SET NOCOUNT ON
INSERT INTO @YourTable VALUES (1,1,1)
INSERT INTO @YourTable VALUES (1,1,2)
INSERT INTO @YourTable VALUES (1,1,3)
INSERT INTO @YourTable VALUES (1,2,1)
INSERT INTO @YourTable VALUES (1,2,2)
INSERT INTO @YourTable VALUES (1,2,3)
INSERT INTO @YourTable VALUES (1,3,1)
INSERT INTO @YourTable VALUES (1,3,2)
INSERT INTO @YourTable VALUES (1,3,3)
INSERT INTO @YourTable VALUES (2,1,1)
INSERT INTO @YourTable VALUES (2,1,2)
INSERT INTO @YourTable VALUES (2,1,3)
INSERT INTO @YourTable VALUES (2,2,1)
INSERT INTO @YourTable VALUES (2,2,2)
INSERT INTO @YourTable VALUES (2,2,3)
INSERT INTO @YourTable VALUES (2,3,1)
INSERT INTO @YourTable VALUES (2,3,2)
INSERT INTO @YourTable VALUES (2,3,3)
INSERT INTO @YourTable VALUES (3,1,1)
INSERT INTO @YourTable VALUES (3,1,2)
INSERT INTO @YourTable VALUES (3,1,3)
INSERT INTO @YourTable VALUES (3,2,1)
INSERT INTO @YourTable VALUES (3,2,2)
INSERT INTO @YourTable VALUES (3,2,3)
INSERT INTO @YourTable VALUES (3,3,1)
INSERT INTO @YourTable VALUES (3,3,2)
INSERT INTO @YourTable VALUES (3,3,3)
SET NOCOUNT OFF

DECLARE @PageNumber     int
DECLARE @PageSize       int
DECLARE @SortBy         int

SET @PageNumber=3
SET @PageSize=5
SET @SortBy=1


--SELECT * FROM @YourTable

--Method 1
;WITH PaginatedYourTable AS (
SELECT
    RowID,Value1,Value2,Value3
        ,CASE @SortBy
             WHEN  1 THEN ROW_NUMBER() OVER (ORDER BY Value1 ASC)
             WHEN  2 THEN ROW_NUMBER() OVER (ORDER BY Value2 ASC)
             WHEN  3 THEN ROW_NUMBER() OVER (ORDER BY Value3 ASC)
             WHEN -1 THEN ROW_NUMBER() OVER (ORDER BY Value1 DESC)
             WHEN -2 THEN ROW_NUMBER() OVER (ORDER BY Value2 DESC)
             WHEN -3 THEN ROW_NUMBER() OVER (ORDER BY Value3 DESC)
         END AS RowNumber
    FROM @YourTable
    --WHERE
)
SELECT
    RowID,Value1,Value2,Value3,RowNumber
        ,@PageNumber AS PageNumber, @PageSize AS PageSize, @SortBy AS SortBy
    FROM PaginatedYourTable
    WHERE RowNumber>=(@PageNumber-1)*@PageSize AND RowNumber<=(@PageNumber*@PageSize)-1
    ORDER BY RowNumber



--------------------------------------------
--Method 2
;WITH PaginatedYourTable AS (
SELECT
    RowID,Value1,Value2,Value3
        ,ROW_NUMBER() OVER
         (
             ORDER BY
                 CASE @SortBy
                     WHEN  1 THEN Value1
                     WHEN  2 THEN Value2
                     WHEN  3 THEN Value3
                 END ASC
                ,CASE @SortBy
                     WHEN -1 THEN Value1
                     WHEN -2 THEN Value2
                     WHEN -3 THEN Value3
                 END DESC
         ) RowNumber
    FROM @YourTable
    --WHERE  more conditions here
)
SELECT
    RowID,Value1,Value2,Value3,RowNumber
        ,@PageNumber AS PageNumber, @PageSize AS PageSize, @SortBy AS SortBy
    FROM PaginatedYourTable
    WHERE 
        RowNumber>=(@PageNumber-1)*@PageSize AND RowNumber<=(@PageNumber*@PageSize)-1
        --AND more conditions here
    ORDER BY
        CASE @SortBy
            WHEN  1 THEN Value1
            WHEN  2 THEN Value2
            WHEN  3 THEN Value3
        END ASC
       ,CASE @SortBy
            WHEN -1 THEN Value1
            WHEN -2 THEN Value2
            WHEN -3 THEN Value3
        END DESC

ВЫХОД:

RowID  Value1 Value2 Value3 RowNumber  PageNumber  PageSize    SortBy
------ ------ ------ ------ ---------- ----------- ----------- -----------
10     2      1      1      10         3           5           1
11     2      1      2      11         3           5           1
12     2      1      3      12         3           5           1
13     2      2      1      13         3           5           1
14     2      2      2      14         3           5           1

(5 row(s) affected

RowID  Value1 Value2 Value3 RowNumber  PageNumber  PageSize    SortBy
------ ------ ------ ------ ---------- ----------- ----------- -----------
10     2      1      1      10         3           5           1
11     2      1      2      11         3           5           1
12     2      1      3      12         3           5           1
13     2      2      1      13         3           5           1
14     2      2      2      14         3           5           1

(5 row(s) affected)
КМ.
источник
1
fyi, при использовании SET SHOWPLAN_ALL ON метод 1 имел TotalSubtreeCost 0,08424953, а метод 2 - 0,02627153. метод 2 был более чем в три раза лучше.
КМ.
1
@rexem, оба метода 1 и 2 используют CTE, способ разбивки на страницы и порядок строк различаются. Я не уверен, почему этот фактический вопрос так отличается от вопроса, на который ссылается OP (в ответе на этот вопрос OP), но мой ответ создает рабочий код на основе ссылки, на которую ссылается OP
KM.
1
Спасибо, я пытаюсь сравнить старый пост и этот ответ. [Я не знаю, как это форматировать] Вот ответ, предоставленный Томалаком. stackoverflow.com/questions/230058?sort=votes#sort-top Это неправильно? Если он отправил только половину ответа, как я буду использовать его более эффективный способ выполнения моего запроса? Пожалуйста, дайте мне еще немного света, чтобы продолжить ... спасибо
@Joseph, выбранный ответ в предоставленной вами ссылке ( stackoverflow.com/questions/230058?sort=votes#sort-top ) отличается от рабочего кода, который задающий вопрос указывает как работающий в своем ответе: stackoverflow.com/ questions / 230058 /… если вы прочитаете этот ответ, вы увидите ссылку на их код: pastebin.com/f26a4b403 и ссылку на их версию Tomalak's: pastebin.com/f4db89a8e в моем ответе. Я предоставляю рабочую версию каждой версии, используя таблица переменных
КМ.
2
WITH MyCte AS 
(
    select 
       employee_id,
       RowNum = row_number() OVER (order by employee_id)
    from V_EMPLOYEE 
)
SELECT  employee_id
FROM    MyCte
WHERE   RowNum > 0
ORDER BY employee_id
sumit
источник
-1
 select salary from (
 select  Salary, ROW_NUMBER() over (order by Salary desc) rn from Employee 
 ) t where t.rn = 2
Азиз Хан
источник
3
Добро пожаловать в Stack Overflow! Хотя этот фрагмент кода может быть решением, включение объяснения действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин вашего предложения кода.
Johan
Пожалуйста, добавьте контекст во фрагмент кода для будущих читателей.
DebanjanB