SQL中的Row_Number()函数在Where子句中的应用

121

我在stackoverflow上找到一个问题,其中使用Row_Number()函数在where子句中回答。当我尝试查询时,出现以下错误:

"Msg 4108, Level 15, State 1, Line 1 Windowed functions can only appear in the SELECT or ORDER BY clauses."

这是我尝试的查询。如果有人知道如何解决,请告诉我。

SELECT employee_id 
FROM V_EMPLOYEE 
WHERE row_number() OVER ( ORDER BY employee_id ) > 0 
ORDER BY Employee_ID

11
ROW_NUMBER() OVER (ORDER BY employee_id) > 0 这个条件将始终被判定为 TRUE - Quassnoi
4
没错,我不担心那个条件,因为我随时可以改变它。我想先让查询正常工作,然后考虑保持行号在500到800之间...谢谢。 - Joseph
2
@Joseph:为什么你要避免使用CTE? - OMG Ponies
1
@rexem - 我不是SQL Server的专家。我正在尝试帮助一个大型项目中的团队,他们面临着许多性能问题。他们正在使用UDF和CTE。在其中一张表中,他们只有5000条记录,如果有5个用户访问搜索,需要超过一分钟才能检索到结果。有时会失败并超时。因此,我正在尝试避免使用CTE和UDF,并尝试提出一个直接的SQL查询,以解决性能问题。 - Joseph
1
大家好, 请查看我下面发布的链接,它以不同的方式回答了如何使用row_number()。有人能比较一下我的初始查询和链接中的查询吗?感谢帮助。 - Joseph
为什么在where子句中不能使用窗口函数?- 举例说明为什么不可能。 - Lukasz Szozda
10个回答

116

为了解决这个问题,将您的选择语句包装在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

8
我试图避免患上慢性脑损伤,那是我担心的最坏情况。谢谢。 - Joseph
4
如果您使用子查询而不是公共表达式,它可能会运行得更快。在某些情况下,我已经看到了性能提高了1.5倍的情况。 - Brian Webster
3
如果在CTE SELECT语句中没有使用TOP关键字,那么SQL 2008服务器将无法执行该查询,因为ORDER BY子句不支持,必须使用TOP关键字。请注意,这里的翻译并不包括任何解释或其他内容。 - Muflix
2
我正在使用SQL2005(呃) - 我可以通过在FROM后删除“ORDER BY”来避免使用“TOP”。 它与OVER后面的(Order By)是多余的。 - Joe B
我一直希望有一种方法可以在没有CTE的情况下在WHERE子句中使用ROW_NUMBER() :( - Jalal
我认为你不需要第二个ORDER BY子句。 - andyabel

73
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


2
@DavideChicco.it:在SQL Server中,派生表需要一个别名(我应该写成AS q,但这样也可以工作)。 - Quassnoi
2
在命名别名时,易读性是我关注的重点。例如,您可以将rn写成RowNumber,将q写成DerivedTable,将where子句写成where DerivedTable.RowNumber> 0。在我看来,当代码不再新鲜于你的脑海中时,这样做会远远减少混淆。 - Edward Comeau
3
现在,"rn"已成为行号的一个普遍被接受的缩写词。尝试在谷歌搜索栏中输入"row_number over as...",看看它会自动提示什么。 - Quassnoi
3
@Quassnoi,良好的编程需要易读性,使用rn(或其它简写别名)需要你和后续维护代码的人员付出认知努力。请注意,Microsoft首次提出了SELECT ROW_NUMBER() OVER(ORDER BY SalesYTD DESC) AS Row,... 我之前也没有遇到过rn,因此在“通用”的情况下可能会有所不同。 - Edward Comeau
1
@Quassnoi,第二个搜索结果是SO文章- https://dev59.com/LHNA5IYBdhLWcg3wcNbF 有几种变化而不是rn;-) - Edward Comeau
显示剩余3条评论

37
Select * from 
(
    Select ROW_NUMBER() OVER ( order by Id) as 'Row_Number', * 
    from tbl_Contact_Us
) as tbl
Where tbl.Row_Number = 5

26

我认为你想要的是这样的:

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作为别名。 - TheTechGuy

14
我觉得所有使用公用表表达式或子查询的解决方法都足以解决这个问题,但我没有看到任何人深入研究OP为什么有问题。原因在于逻辑查询处理顺序:

  1. FROM
  2. ON
  3. JOIN
  4. WHERE
  5. GROUP BY
  6. WITH CUBE/ROLLUP
  7. HAVING
  8. SELECT
  9. DISTINCT
  10. ORDER BY
  11. TOP
  12. OFFSET/FETCH

我认为这对答案的贡献很大,因为它解释了为什么会出现像这样的问题。在处理许多函数时,WHERE总是在SELECT之前进行处理,这就需要使用公用表表达式或子查询。你会经常在SQL Server中看到这种情况。


10

针对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替换为内联视图或反之亦然的情况下是如此。
当然,您可以在自己的系统上尝试自己的查询,看看是否有差异。
此外,在where子句中使用row_number()是Stack Overflow上常见的错误。逻辑上,只有在处理select子句时才能使用row_number()。人们经常忘记这一点,当他们回答问题而没有测试答案时,答案有时是错误的。(这是我自己也犯过的错误。)

1
谢谢,Shannon。你使用的 SQL Server 版本是哪个? - OMG Ponies
1
那就是说,该链接提供的答案是错误的?但是,提出问题的人同意它是有效的.. 令人惊讶.. :-) - Joseph
2
@ Joseph,但是如果您看一下链接问题中 OP 发布的另一个答案,您会发现他链接到的代码版本与被接受的答案不同。我不知道他为什么接受了这个答案,即使代码输入无法运行。也许在接受后的某个时刻进行了编辑,或者即使不完全正确也足以让他继续前进。 - Shannon Severance
1
@Rexem: 支持CTE或ROW_NUMBER()的是SQL Server 2005和SQL Server 2008。早期版本不支持。 - Shannon Severance

6
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

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

1
使用CTE还是子查询性能更好?谢谢。 - Joseph
2
请查看Shannon的答案 - 在他的测试中它们是相等的。 - OMG Ponies
6
不,它并不更快。在SQL Server中,CTE和内联视图是相同的东西,并且性能相同。当在CTE中使用非确定性函数时,它会在每次调用时重新计算。必须使用一些技巧来强制执行CTE的材料化。请参阅我博客中的这些文章:http://explainextended.com/2009/07/28/sql-server-random-records-avoiding-cte-reevaluation/ http://explainextended.com/2009/05/28/generating-xml-in-subqueries/ - Quassnoi

2

根据问题的回答者所说:

请查看此链接。它有一个不同的解决方案,对于提出问题的人来说似乎有效。我正在尝试找到这样的解决方案。

在SQL Server 2005中使用ROW_NUMBER() OVER ()进行排序的分页查询

~Joseph

"方法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
提醒一下,使用_SET SHOWPLAN_ALL ON_方法时,方法1的TotalSubtreeCost为0.08424953,而方法2为0.02627153。方法2比方法1好三倍以上。 - KM.
1
@rexem,方法1和方法2都使用CTE,它们分页和排序行的方式不同。我不确定为什么这个实际问题与OP链接到的问题如此不同(在OP的答案中回答了这个问题),但我的答案基于OP提供的链接创建了可工作的代码。 - KM.
1
谢谢,我正在尝试比较旧的帖子和这个答案。[我不知道如何格式化] 这是Tomalak提供的答案。 https://dev59.com/4EXRa4cB1Zd3GeqPs5bh?sort=votes#sort-top这样做有问题吗?如果他只发布了答案的一半,我该如何继续使用他更好的查询性能方式?请给我一些进一步的指导... 谢谢 - Joseph
@Joseph,你提供的链接中所选的答案(https://dev59.com/4EXRa4cB1Zd3GeqPs5bh?sort=votes#sort-top)与提问者在他们的回答中提供的工作代码不同:https://dev59.com/4EXRa4cB1Zd3GeqPs5bh/paginated-query-using-sorting-on-different-columns-using-rownumber-over-in/240108#240108 如果你阅读那个回答,你会看到他们的代码链接:http://pastebin.com/f26a4b403 和他们版本的Tomalak's链接:http://pastebin.com/f4db89a8e 在我的回答中,我使用表变量提供了每个版本的工作版本。 - KM.

0
 select salary from (
 select  Salary, ROW_NUMBER() over (order by Salary desc) rn from Employee 
 ) t where t.rn = 2

3
欢迎来到 Stack Overflow!虽然这段代码片段可能是解决方案,但包括一些解释真的有助于提高您的帖子质量。请记住,您正在为未来的读者回答问题,而这些人可能不知道您建议使用代码的原因。 - Johan
请为未来的读者添加一些上下文到代码片段中。 - undetected Selenium

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接