使用具有无限多个条件的 SQL 服务器存储过程而不是视图

Use a SQL Server stored procedure with unlimited number of multiple criteria instead of a view

我有一个页面,用户 select 多个搜索条件从 SQL Server 2014 视图中检索数据。该视图正在从链接服务器上的 table 抓取数据(我无法将视图直接放在该服务器上,而我正在读取的 table 有超过 8 亿行,因此复制该数据到本地服务器上是不会发生的)。

当然,我也无法为视图编制索引(在链接服务器上),所以我试图找到一种方法来阻止在查询为 运行 时发生超时。是否可以在存储过程中做这样的事情?

SELECT 
    cast(trees as varchar(3)) as Trees
    , MIN(fruitnumber) AS FN_Start
    , MAX(fruitnumber) AS FN_End
    , COUNT(CASE WHEN fruitType = 'apple' THEN 1 ELSE NULL END) AS apple
    , COUNT(CASE WHEN fruitType = 'banana' THEN 1 ELSE NULL END) AS banana
FROM 
    view_fruitReport 
WHERE 
    (orchard = @orchard) and 

这就是它变得不稳定的地方。用户 select 下拉列表中的果园(不是组合框,因为我们使用 IE11 和 ajaxtoolkit 组合框在那里仍然不起作用)所以只有一个 selection 可能但是。

他们能够向列表框添加条件。无限的标准。而且他们不需要 select 任何条件,他们可以按果园搜索。

因此 WHERE 子句的其余部分是根据他们添加到列表框中的内容构建的。

像这样:

' check if items selected in both listboxes'
If trees_Listbox.Items.Count > 0 Then
    If fruitminListBox.Items.Count > 0 Then
        'cycle through items in fruitnum listbox to create an "in" clause for sql query'
        For Each item As ListItem In trees_Listbox.Items
            whereString += String.Join(",", item) + ", "
        Next
        whereString = Left(whereString, Len(whereString) - 2) + ")"

        selectQry += "(" + wherecls + whereString + ")"
        whereFNcls = "(fruitNumber between "
        For Each itemFNmin As ListItem In fruitminListBox.Items
            'create a "between" clause for the min and max FN values entered by user.'
            whereOEcls += itemFNmin.Value + " and " + fruitmaxListBox.Items(i).ToString + ") or (fruitNumber between " '(fruitnumber between number and number) or '
            i += 1
        Next
        'trim off the last text portion of the whereOEcls'
        whereOEcls = Left(whereOEcls, Len(whereFNcls) - 25)
        selectQry += " and (" + whereFNcls + ") GROUP BY trees ORDER BY trees"

        fruityData.SelectCommand = selectQry


        WeeklyGridView.Visible = True

    Else
        'see if FN is empty but trees is selected'
        For Each item As ListItem In trees_Listbox.Items
            whereString += String.Join(",", item) + ", "
        Next
        whereString = Left(whereString, Len(whereString) - 2)
        selectQry += wherecls + whereString + ") GROUP BY trees ORDER BY trees"

        fruityData.SelectCommand = selectQry
        WeeklyGridView.Visible = True
    End If
Else

基本上以 where 子句结束,可能如下所示:

WHERE (orchard = @orchard) 
  and trees in (100,200,300,400) 
  and fruitnumber between (itemFNmin.Value and itemFNmax.Value) 
   or fruitnumber between (itemFNmin.Value and itemFNmax.Value) 
  etc etc etc

除了让事情变得非常丑陋之外,这很有效,我敢肯定这是一种糟糕的做法。

我不知道 if/how 我可以将这些变量列表作为多个数组或 tables 等传递给存储过程

可能任何事情都比将它们绑定到视图更好,其链接服务器 table 甚至没有索引 table(不是我的错哈哈)

也许有用户体验答案。当您 知道 它不会在很长一段时间内返回时,提醒用户并确认他们是否要等待。 (确认上带有复选框 "don't show me this again.")

但是不要重新记录 Windows Explorer 中可怕的估计文件传输时间。

对于您的第一个问题:您可以 return 每种水果类型的计数,但它会对性能产生影响,因为它需要对每种水果进行子查询。这还要求您在查询中对每种可能的水果类型进行硬编码。我假设水果类型可以改变或添加其他类型,所以这在维护方面也不是最理想的。除非您在 proc 中构建 SQL 并使用 sp_executesql,否则您不能向查询动态添加列,这比在 .Net 代码中的 SQL 行更复杂。

SELECT 
    cast(trees as varchar(3)) as Trees
    , MIN(fruitnumber) AS FN_Start
    , MAX(fruitnumber) AS FN_End
    , CASE
        WHEN fruitType = 'apple' THEN (SELECT COUNT(fruitType) FROM view_fruitReport WHERE fruitType = 'apple') ELSE NULL
      END AS [apple]    
    , CASE
        WHEN fruitType = 'banana' THEN (SELECT COUNT(fruitType) FROM view_fruitReport WHERE fruitType = 'banana') ELSE NULL
      END AS [banana]         
FROM 
    view_fruitReport 
WHERE 
    (orchard = @orchard) 

对于第二个问题,您可以将 lists/tables 传入存储过程。一种方法是传递某种定界字符串并使用 T-SQL 解析它。但是,我推荐一种不同的方法,即 Table Value Parameters。这是一个用作 table 的参数,您可以在存储过程中加入它。

这里是为 Trees 列实施 Table 值参数的示例。

您首先需要声明一个 SQL 类型:

CREATE TYPE [dbo].[Trees] AS TABLE (Trees INT)

然后您可以在存储过程中将其作为参数引用,充当 table。请注意,您不能将 WITH(NOLOCK) 与这些一起使用,并且必须在参数中指定 READONLY:

CREATE PROCEDURE [dbo].[up_getOrchardInfo]
(
      @Trees As [dbo].[Trees] READONLY
    , @Orchard  INT
)
AS
BEGIN

    SELECT 
        cast(trees as varchar(3)) as Trees
        , MIN(fruitnumber) AS FN_Start
        , MAX(fruitnumber) AS FN_End
        , COUNT(CASE WHEN fruitType = 'apple' THEN 1 ELSE NULL END) AS apple
        , COUNT(CASE WHEN fruitType = 'banana' THEN 1 ELSE NULL END) AS banana
    FROM 
        view_fruitReport AS F
            INNER JOIN @Trees AS T
            ON F.Trees = T.Trees
    WHERE 
        (orchard = @orchard)

END

GO

以上示例将按传入的 Trees 进行过滤。请注意,如果 @Trees 为 Null 或计数为 0,如果想要 return Orchard 的所有内容,则需要在您的存储过程。

IF (@Trees IS NULL OR (SELECT COUNT(1) FROM @Trees = 0))
    BEGIN
        --No Join to @Trees
    END
    ELSE
        BEGIN
            --Query from above.
        END

最后,在 .Net 端,您需要传入数据Table 对象作为结构化类型的 SqlCommand 的参数:

Dim sqlCommand As New SqlCommand("up_getOrchardInfo", sqlConnection.SqlConnection)

sqlCommand.CommandType = CommandType.StoredProcedure

Dim sqlTreesParameter As New SqlParameter("@Trees", SqlDbType.Structured)
sqlOrchardParameter.Direction = ParameterDirection.Input

Dim tblExample As New DataTable

tblExample.Columns.Add("Trees", New Integer().GetType())

Dim drExample As DataRow = tblExample.NewRow()
drExample.Item("Trees") = 100

tblExample.Rows.Add(drExample)

'Adjust if Orchard is a VarChar/String'
Dim sqlOrchardParameter As New SqlParameter("@Orchard", SqlDbType.Int)  
sqlOrchardParameter.Direction = ParameterDirection.Input
sqlOrchardParameter.Value = intYourOrchardValue

sqlCommand.Parameters.Add(sqlTreesParameter)    
sqlCommand.Parameters.Add(sqlOrchardParameter)

'Execute Dataset

综上所述,您可能要考虑制作多个存储过程。一个存储过程可以针对 return 仅传递 Orchard 时的所有内容进行优化,而另一个则针对还传递 Trees 时进行优化。这取决于您要处理的参数数量。