【问题标题】:Convert SET ROWCOUNT n to TOP (n) when n could be 0当 n 可能为 0 时,将 SET ROWCOUNT n 转换为 TOP (n)
【发布时间】:2018-02-24 22:52:05
【问题描述】:

我们目前使用的是 SQL Server 2016。根据 Microsoft 页面 Deprecated Database Engine Features in SQL Server 2016,SET ROWCOUNT 已被弃用。所以我们试图将所有SET ROWCOUNT N 语句转换为TOP(N) 语句。看起来很简单,但是我们遇到了一个情况,N是存储过程中的参数,默认为0。

所以在旧代码中,它类似于SET ROWCOUNT @NumRows。如果@NumRows 为0,则表示关闭SET ROWCOUNT 选项,因此以下查询将返回所有行。但是如果我们将其转换为TOP(@NumRows),这意味着它将返回 0 行。为了避免这个问题,我们可以添加额外的 IF 条件,如果 @NumRows 为 0,则将 @NumRows 设置为一个巨大的数字。或者我们可以添加额外的 IF 条件,如果 @NumRows 为 0,那么我们使用不带 TOP 的 SELECT,否则我们照常使用 SELECT TOP(N)。

但是这些解决方案中的任何一个都会在存储过程中添加额外的代码,所以我的问题是:考虑到 N 可能为 0,有没有一种优雅的方法可以将 SET ROWCOUNT N 转换为 TOP (N)?

更新:添加存储过程模板

-- Default to 0, in this case, SET ROWCOUNT 0 will return all result
-- But if we change it to TOP(@rows), it returns no result.
CREATE PROCEDURE Proc1  
  @rows int = 0  
AS  
SET ROWCOUNT @rows
SELECT Col1 FROM table1
ORDER BY Col2
SET ROWCOUNT 0

-- In this case, the default is not 0
-- But the program that calls this stored procedure could pass in value 0. 
-- We also don't want to change default value for this stored procedure. 
-- So I think in this case, we may have to add IF @rows = 0, SET @rows = huge_number
CREATE PROCEDURE Proc2
  @rows int = 10
AS
SET ROWCOUNT @rows
SELECT Col3 FROM table2
ORDER BY Col4
SET ROWCOUNT 0

【问题讨论】:

  • 优雅?不,但是您可以在将其插入您的 top 子句之前说 select @NumRows = iif(@NumRows = 0, 2147483648, @NumRows)。也就是说,这很可能会给您带来糟糕的查询计划。除非您有数千个需要查找/替换的过程,否则您可能需要考虑指定两个不同代码路径的额外工作。您的里程可能会有所不同。
  • 我会警告不要使用多个执行路径。它会导致非常有趣的性能问题。 Gail Shaw 在这里有一篇关于这个主题的精彩博文。 sqlinthewild.co.za/index.php/2009/09/15/…
  • @SeanLange 感谢您的链接。我通读了它并理解了不同执行计划的可能性。因此,如果我不使用不同的执行路径,那意味着我将始终使用TOP(N)。然后我需要检查N值是否为0。如果为0,我可以从表中生成NumRows = 2147483647 或NumRows = count(*),这对查询计划有什么影响?
  • SET ROWCOUNT 不推荐用于 SELECT 语句,仅适用于 INSERT、UPDATE 和 DELETE

标签: sql-server tsql sql-server-2016 sql-limit


【解决方案1】:

听起来你的过程定义是这样的。如果您发布定义,在这里提供帮助会容易得多。

create procedure MyProc
(
    @NumRows int = 0
)
as 
    if @NumRows > 0
        set rowcount @NumRows
    else
        set rowcount 0

    select Columns
    from Table

假设定义和上面一样,你可以把它改成这样。

create procedure MyProc
(
    @NumRows int = 2147483648 --max value for an int. This could be a bigint also if you have LOTS of data.
)
as 
    select top(@NumRows) Columns
    from Table

请注意,使用 TOP 时,您需要指定 order by,否则您无法知道将返回哪些行。

--编辑--

感谢您提供代码示例。看来我在这里的猜测非常接近。您不必在此处使用 IF 语句,您可以直接在查询中更轻松地完成此操作。这类似于 lad2025 发布的答案。

select top(isnull(nullif(@NumRows, 0), 2147483647)) Columns
from Table

【讨论】:

  • 感谢您将默认设置为 2147483648 的建议。我还编辑了原始问题以包括存储过程的外观。我认为如果当前默认值为 0,我们可以将其更改为 2147483648。但是如果当前默认值不是 0,那么我们应该这样做,if @NumRows = 0, set @NumRows = 2147483648?或者只是在所有默认情况下都使用这个 IF 语句?
  • 查看我的编辑以获得比在此处使用 IF 语句更简单的方法。
【解决方案2】:

您可以在TOP 子句中使用子查询/表达式而不是特定值:

DECLARE @param INT = 0;

SELECT TOP (SELECT IIF(@param=0,2000000, @param)) * FROM sys.objects;
SELECT TOP (IIF(@param=0,2000000, @param)) * FROM sys.objects;

Rextester Demo

注意可能的性能影响。同样TOP 没有明确的ORDER BY 可能会返回不同的结果集。

【讨论】:

  • 感谢您的回答!您能否详细说明可能的性能影响?如果您可以分享一些链接,我也可以阅读这些链接。
猜你喜欢
  • 1970-01-01
  • 2023-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多