是否有任何现有的,优雅的可选TOP条款模式?

时间:2017-03-07 01:20:22

标签: sql sql-server

采用此处定义的(简化)存储过程:

create procedure get_some_stuffs
  @max_records int = null
as
begin
  set NOCOUNT on

  select top (@max_records) *
  from my_table
  order by mothers_maiden_name
end

我希望仅在提供 @max_records时限制所选记录的数量。

问题:

  1. 真正的查询令人讨厌且大;我想避免重复类似于此:

    if(@max_records is null)
    begin
      select *
      from {massive query}
    end
    else
    begin
      select top (@max_records)
      from {massive query}
    end
    
  2. 任意哨兵值感觉不对:

    select top (ISNULL(@max_records, 2147483647)) *
    from {massive query}
    

    例如,如果@max_recordsnull{massive query}返回的行数少于2147483647,则与以下内容相同:

    select * 
    from {massive query}
    

    或者从只有50行的表格中选择top (2147483647) *是否会受到某种惩罚?

  3. 是否有其他现有模式允许可选的计数限制结果集而不重复查询或使用sentinel值?

5 个答案:

答案 0 :(得分:2)

我正在考虑这个问题,虽然我喜欢IF声明中Problem 1语句的明确性,但我理解重复的问题。因此,您可以将主查询放在单个CTE中,并使用一些技巧从中查询(粗体部分是此解决方案的亮点):

CREATE PROC get_some_stuffs
(
    @max_records int = NULL
)
AS
BEGIN
    SET NOCOUNT ON;

    WITH staged AS (
        -- Only write the main query one time
        SELECT * FROM {massive query}
    )
    -- This part below the main query never changes:
    SELECT * 
    FROM (
        -- A little switcheroo based on the value of @max_records
        SELECT * FROM staged WHERE @max_records IS NULL
        UNION ALL
        SELECT TOP(ISNULL(@max_records, 0)) * FROM staged WHERE @max_records IS NOT NULL
    ) final
    -- Can't use ORDER BY in combination with a UNION, so move it out here
    ORDER BY mothers_maiden_name
END

我查看了每个的实际查询计划,优化器非常智能,完全避免了不需要运行的UNION ALL部分。

ISNULL(@max_records, 0)就在那里,因为TOP NULL无效,而且无法编译。

答案 1 :(得分:1)

有一些方法,但正如你可能会注意到这些方法看起来都很难看或者是不必要的复杂。此外,你真的需要ORDER BY吗?

您可以使用TOP (100) PERCENT和一个视图,但PERCENT仅在您不需要那么昂贵的ORDER BY时才有效,因为如果您将SQL Server忽略您的ORDER BY试试吧。

我建议利用存储过程,但首先让我们解释一下procs类型的区别:

硬编码参数嗅探

--Note the lack of a real parametrized column. See notes below.
IF OBJECT_ID('[dbo].[USP_TopQuery]', 'U') IS NULL
    EXECUTE('CREATE PROC dbo.USP_TopQuery AS ')
GO
ALTER PROC [dbo].[USP_TopQuery] @MaxRows NVARCHAR(50)
AS
BEGIN
DECLARE @SQL NVARCHAR(4000) = N'SELECT * FROM dbo.ThisFile'
      , @Option NVARCHAR(50) = 'TOP (' + @MaxRows + ') *'
IF ISNUMERIC(@MaxRows) = 0
    EXEC sp_executesql @SQL     
ELSE
    BEGIN
        SET @SQL = REPLACE(@SQL, '*', @Option)
        EXEC sp_executesql @SQL
    END
END

局部变量参数嗅探

IF OBJECT_ID('[dbo].[USP_TopQuery2]', 'U') IS NULL
    EXECUTE('CREATE PROC dbo.USP_TopQuery2 AS ')
GO
ALTER PROC [dbo].[USP_TopQuery2] @MaxRows INT NULL
AS
BEGIN
DECLARE @Rows INT;
    SET @Rows = @MaxRows;

IF @MaxRows IS NULL
    SELECT *
    FROM dbo.THisFile   
ELSE
    SELECT TOP (@Rows) *
    FROM dbo.THisFile
END

无参数嗅探,旧方法

IF OBJECT_ID('[dbo].[USP_TopQuery3]', 'U') IS NULL
    EXECUTE('CREATE PROC dbo.USP_TopQuery3 AS ')
GO
ALTER PROC [dbo].[USP_TopQuery3] @MaxRows INT NULL
AS
BEGIN

IF @MaxRows IS NULL
    SELECT *
    FROM dbo.THisFile   
ELSE
    SELECT TOP (@MaxRows) *
    FROM dbo.THisFile
END
  

请注意参数SNIFFING:

     

SQL Server在编译时时初始化存储过程中的变量,而不是在它解析时。

     

这意味着SQL Server将无法猜测查询   无论如何,请为查询选择上一个有效执行计划   是否好。

有两种方法,硬编码允许优化器猜测的局部变量。

  1. 参数嗅探的硬编码

    • 使用sp_executesql不仅可以重用查询,还可以防止SQL注入。
    • 但是,在这种类型的查询中,由于TOP运算符不是列或表(因此该语句在我使用的版本中实际上没有变量),因此不会总是表现得更好。
    • 创建编译计划时的统计数据将决定如果您不在谓词上使用变量(ONWHEREHAVING),该方法的效果如何
    • 可以使用选项或提示RECOMPILE来解决此问题。
  2. 变量参数嗅探

    • 另一方面,变量Paramter嗅探足够灵活,可以处理这里的统计数据,在我自己的测试中,似乎变量参数具有使用统计信息的查询的优势(特别是在我更新统计数据之后)。 / LI>
  3. Top Queries

    • 最终,性能问题是关于哪种方法将使用最少量的步骤遍历传单。表格中的统计信息以及SQL Server决定使用“扫描与搜索”时的规则会影响性能。

    • 运行不同的值会显着改变性能,但通常会优于USP_TopQuery3。所以不要假设一种方法必然比另一种更好。

      • 另请注意,您可以使用表值函数来执行相同操作,但正如Dave Pinal所说:
      

    如果您要回答'为避免重复代码,请使用   功能' - 请更加努力!存储过程也可以这样做......

         

    如果你要回答   '可以在SELECT中使用函数,而存储过程则不能   使用' - 再次思考!

         

    SQL SERVER – Question to You – When to use Function and When to use Stored Procedure

答案 2 :(得分:0)

你可以这样做(使用你的例子):

create procedure get_some_stuffs
  @max_records int = null
as
begin
  set NOCOUNT on

  select top (ISNULL(@max_records,1000)) *
  from my_table
  order by mothers_maiden_name
end

我知道你不喜欢这样(根据你的观点2),但这完全取决于它是如何完成的(根据我的经验)。

答案 3 :(得分:0)

这样的事情怎么样(你必须真正看看执行计划,我没有时间设置任何东西)?

create procedure get_some_stuffs
  @max_records int = null
as
begin
  set NOCOUNT on

  select *, ROW_NUMBER(OVER order by mothers_maiden_name) AS row_num
  from {massive query}
  WHERE @max_records IS NULL OR row_num < @max_records
end

使用{large query}可以做的另一件事是创建一个视图或内联表值函数(它是参数化的),对于任何大型和重复使用的函数来说,这通常都是一个很好的做法。

答案 4 :(得分:0)

您可以使用SET ROWCOUNT

 var Rows = $find('<%= RadGrid1.ClientID %>').get_masterTableView().get_dataItems();
 var Clicked_rowIndex = object.parentNode.parentNode.rowIndex;
 var row = Rows[Clicked_rowIndex-1];
 id = row.getDataKeyValue("id");
 alert(id);