来自param或变量的SQL WHERE IN

the*_*bit 6 sql vb.net sql-server stored-procedures

上下文

我们目前正在清理SQL数据库,我们遇到了大量的存储过程,它们之间只有细微的差别.我们希望将它们整合到一个单一的过程中,以便于维护.

问题

下面只是我们尝试合并的那种存储过程的两个例子(请注意,这些是简化版本,而不是实际的过程).

存储过程 - 当前预订

ALTER PROCEDURE [dbo].[SelectCurrentBookings]
    @client_FK INT, @startDate DATETIME, @endDate DATETIME
AS
BEGIN
    SELECT  ROW_NUMBER() OVER (ORDER BY [Booking].[bookingDateTime])            [RowNumber],
            [Booking].[booking_PK]      [Booking ID],
            [Booking].[bookingDateTime] [Booking Date],
            [Booking].[bookingDuration] [Duration],
            [Booking].[client]          [Client Name],
            CASE WHEN [Booking].[bookingStatusCode_FK] IN (4,8,9,7,16) THEN 1 ELSE 0 END    [Unserviced],
    FROM    [Booking]
    WHERE   [client_FK] = @client_FK
            AND [Booking].[bookingStatusCode_FK] IN (1,2,14,17)
            AND [Booking].[bookingDateTime] >= @startDate
            AND [Booking].[bookingDateTime] < DATEADD(d,1,@endDate)
            AND [Booking].[deleted] = 0
END
Run Code Online (Sandbox Code Playgroud)

存储过程 - 存档预订

ALTER PROCEDURE [dbo].[SelectArchivedBookings]
    @client_FK INT, @startDate DATETIME, @endDate DATETIME
AS
BEGIN
    SELECT  ROW_NUMBER() OVER (ORDER BY [Booking].[bookingDateTime])            [RowNumber],
            [Booking].[booking_PK]      [Booking ID],
            [Booking].[bookingDateTime] [Booking Date],
            [Booking].[bookingDuration] [Duration],
            [Booking].[client]          [Client Name],
            CASE WHEN [Booking].[bookingStatusCode_FK] IN (4,8,9,7,16) THEN 1 ELSE 0 END    [Unserviced],
    FROM    [Booking]
    WHERE   [client_FK] = @client_FK
            AND [Booking].[bookingStatusCode_FK] IN (1,2,14,4,9,7,16,13)
            AND [Booking].[bookingDateTime] >= @startDate
            AND [Booking].[bookingDateTime] < DATEADD(d,1,@endDate)
            AND [Booking].[deleted] = 0
END
Run Code Online (Sandbox Code Playgroud)

调用存储过程的代码在VB.NET中

Dim Command As DbCommand = _db.GetStoredProcCommand("SelectCurrentBookings")
_db.AddInParameter(Command, "client_FK", DbType.Int32, ClientID)
_db.AddInParameter(Command, "startDate", DbType.DateTime, StartDate)
_db.AddInParameter(Command, "endDate", DbType.DateTime, EndDate)
Return _db.ExecuteDataSet(Command)
Run Code Online (Sandbox Code Playgroud)

如您所见,上述存储过程之间的唯一区别是提供给WHERE IN的值.

有没有办法让我们修改它并通过参数或变量提供值列表?

Mic*_*een 1

如果目标是减少维护工作,我会谦虚地建议将数据值移出数据层并将它们硬编码到逻辑层中(也许在几个地方),这可能无助于实现这一目标。

从查询中删除这些显式值将删除优化器可用于创建计划的信息。请小心将其替换为更好的东西,否则查询性能可能会受到影响。我认为这些 SP 是精确分离的,因此每个 SP 都存在独特的计划,特别是当查询比显示的复杂得多时。将当前的计划与其他计划以及最终的结果进行比较,以确保您没有倒退。

一种选择可能是创建一个新的“列表”表:

ListName  StatusCode
current   1
current   2
...
current   17
archive   1
archive   2
archive   4
...
archive   16
Run Code Online (Sandbox Code Playgroud)

连接到该表而不是使用 IN 子句。通过 ListName 限定连接,该名称作为参数传递。(ListName, StatusCode) 上的唯一聚集索引会很好。您可以考虑为每个 ListName 创建一个过滤统计数据。如果您拥有状态值的主列表,请创建外键约束。

存储过程则变为

ALTER PROCEDURE [dbo].[SelectCurrentBookings]
    @client_FK INT, @startDate DATETIME, @endDate DATETIME, 
    @ListName char(10)
AS
BEGIN
    SELECT  ROW_NUMBER() OVER (ORDER BY [Booking].[bookingDateTime]) [RowNumber],
            ...
    FROM    [Booking]
    INNER JOIN dbo.List as l
        ON [Booking].[bookingStatusCode_FK] = l.StatusCode
        AND l.ListName = @ListName 
    WHERE   [client_FK] = @client_FK
            AND [Booking].[bookingDateTime] >= @startDate
            AND [Booking].[bookingDateTime] < DATEADD(d,1,@endDate)
            AND [Booking].[deleted] = 0
END
Run Code Online (Sandbox Code Playgroud)

调用代码获得一个参数

Dim Command As DbCommand = _db.GetStoredProcCommand("SelectCurrentBookings")
_db.AddInParameter(Command, "client_FK", DbType.Int32, ClientID)
_db.AddInParameter(Command, "startDate", DbType.DateTime, StartDate)
_db.AddInParameter(Command, "endDate", DbType.DateTime, EndDate)
_db.AddInParameter(Command, "ListName", DbType.String, "current") //correct type needed
Return _db.ExecuteDataSet(Command)
Run Code Online (Sandbox Code Playgroud)

通过这种方式,状态代码的含义被记录在一个地方,并且优化器可以获得良好的统计数据。这是否比当前的实现更快,只有测试才能知道。