【问题标题】:T-SQL procedure - filter parameter as Object/CLR/Xml/UDTT-SQL 过程 - 过滤参数为 Object/CLR/Xml/UDT
【发布时间】:2012-05-18 10:17:47
【问题描述】:

悬崖:是否有一种已知模式可以将标准“过滤器”类型传递给存储过程以封装 stardate/enddate/pagesize/pagenum 参数?

不确定这个问题的正确位置。我正在探索将过滤对象参数传递给存储过程的想法,该存储过程封装了我们常见的过滤参数(开始日期、结束日期、页码、页面大小、整数列表等)。这样做的原因是为了减少在我们的过程中散布的类似参数和样板 SQL 的数量。这将为我们从一开始就为每个过程提供一个更标准的界面和起点。我无法找到有关该主题的太多信息。

我注意到的模式 - 在第一次构建大多数 SP 时,它们从 where 子句中使用的单个 id 参数开始。稍后,您可能需要为日期范围参数添加参数(开始日期、结束日期或动态范围“ytd、mtd、dtd”)。如果数据集足够大,您可能还需要为服务器端分页引入 pagesize/pagenum。一段时间后,您可能会意识到您需要一个 id 列表而不是单个 id 的结果,因此您添加了一个 CSV 或 XML 参数来封装这些 ID。

最终,许多存储过程最终都会有许多类似的样板文件和(希望)相同的参数来处理这些标准过滤参数。我正在尝试研究将封装的过滤器对象参数传递给我的程序的已知模式,理想情况下,该模式将在 C# 端进行强类型化。这在管理一组为报告提供动力的过程时特别有用,这些过程都需要相同的过滤选项(除了特定于报告的查询参数之外)。

我的目标是将所需的参数数量减少到 WHERE 子句所需的最低限度,并创建一个标准机制,用于将通用过滤选项传递到过程中并在过程中使用这些值。这如何通过 XML 或 CLR 或 UDT 参数实现?

对于这个问题的上下文,我通过 C# 2.0 的 ADO.Net 使用 SQL Server 2008。不幸的是,LINQ/EF 目前还不是这个项目的选项,我们必须坚持使用现有的 RDBMS。如果有一种已知的模式需要改变技术,我会很想知道它。

编辑:感谢到目前为止的回复。我已经添加了 50 分的赏金,我将再运行几天以尝试促进更多讨论。如果我的问题不够清楚,请发表评论..

【问题讨论】:

  • 我考虑过的一个潜在解决方案是一个 xml 序列化的 C# 对象,它使用 UDF 反序列化为数据表。在每个过程中通过键名找出适当的值仍然很麻烦。最后我只是想对这个概念做更多的研究——它可以在其他平台上使用吗?是否有人开发了过滤层/api 来使 SQL Server 更容易做到这一点?提前致谢。
  • 我使用了很多动态SQL。在这种情况下,通过传入 WHERE 子句即可解决问题。
  • 感谢您的意见。我们已经做了很多动态 sql,实际上这个项目的所有 SQL 都存储在应用程序中,我们在其中附加 SqlDataParameters 并根据需要执行我们自己专有的 WHERE 子句注入。然而,每个 SQL 块最终都有很多重复的参数和逻辑以满足通常的过滤需求,我试图找到一种将这些封装成单个参数的模式。

标签: sql sql-server-2008 tsql parameters filtering


【解决方案1】:

我个人认为你想太多或试图减少一些不需要减少的东西。最好不要单独使用存储过程参数,或者尝试创建一些可以将参数集附加到命令对象的基类和辅助函数。

不过,话虽如此,我会为你的问题提供一个解决方案,看看它是否符合你的需求:

我建议使用 TSQL 用户定义类型。创建一种或多种类型。也许一个用于日期范围,一个用于分页和排序。我使用类似的过程将多行数据传递给存储过程。 (其中一些代码可能需要稍微调整一下,因为我只是在修改一些我已经编写的代码,而且我已经有一段时间没有使用 DataTable 字段了。)

最终,所有这些都是缩短应用程序方法和匹配存储过程中的参数列表。存储过程将负责提取或连接表变量中的信息。下面列出的类确实提供了在 .NET 应用程序端保持这些参数强类型化的能力。

if not exists (select * from INFORMATION_SCHEMA.DOMAINS where DOMAIN_SCHEMA = 'dbo' and DOMAIN_NAME = 'DateRange' and DATA_TYPE = 'table type')
begin

    create type dbo.DateRange as table 
    (
        StartDate datetime2 null
        ,EndDate datetime2 null
    )

end
go


if not exists (select * from INFORMATION_SCHEMA.DOMAINS where DOMAIN_SCHEMA = 'dbo' and DOMAIN_NAME = 'Paging' and DATA_TYPE = 'table type')
begin

    create type dbo.Paging as table 
    (
        PageNumber int null
        ,PageSize int null
        ,SortField sysname null
        ,SortDirection varchar(4) null
    )

end
go

SQL 用户定义类型可以表示为 .NET 应用程序中的强类型对象。从基类开始:

    Imports System
    Imports System.Data
    Imports System.Data.SqlClient
    Imports System.Runtime.Serialization


    Namespace SqlTypes

        <Serializable()> _
        <System.ComponentModel.DesignerCategory("Code")> _
        Public MustInherit Class SqlTableTypeBase
            Inherits DataTable

            Public Sub New()

                MyBase.New()
                Initialize()

            End Sub


            Public Sub New(ByVal tableName As String)

                MyBase.New(tableName)
                Initialize()

            End Sub


            Public Sub New(ByVal tableName As String, ByVal tableNamespace As String)

                MyBase.New(tableName, tableNamespace)
                Initialize()

            End Sub


            Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)

                MyBase.New(info, context)

            End Sub


            ''' <summary>
            ''' Implement this method to create the columns in the data table to match the SQL server user defined table type
            ''' </summary>
            ''' <remarks></remarks>
            Protected MustOverride Sub Initialize()


            Public Function CreateParameter(parameterName As String) As SqlParameter

                Dim p As New SqlParameter(parameterName, SqlDbType.Structured)
                p.Value = Me

                Return p

            End Function

        End Class

    End Namespace

为 SQL 类型创建一个实现:

Imports System
Imports System.Data
Imports System.Runtime.Serialization


Namespace SqlTypes

    <Serializable()> _
    <System.ComponentModel.DesignerCategory("Code")> _
    Public Class DateRange
        Inherits SqlTableTypeBase

        Public Sub New()

            MyBase.New()

        End Sub


        Public Sub New(ByVal tableName As String)

            MyBase.New(tableName)

        End Sub


        Public Sub New(ByVal tableName As String, ByVal tableNamespace As String)

            MyBase.New(tableName, tableNamespace)

        End Sub


        Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)

            MyBase.New(info, context)

        End Sub


        'TODO: throw some more overloaded constructors in here...

        Public Sub New(startDate As DateTime?, endDate As DateTime?)

            MyBase.New()

            Me.StartDate = startDate
            Me.EndDate = endDate

        End Sub


        Public Property StartDate As DateTime?
            Get
                Return CType(Me.Rows(0)(0), DateTime?)
            End Get
            Set(value As DateTime?)
                Me.Rows(0)(0) = value
            End Set
        End Property


        Public Property EndDate As DateTime?
            Get
                Return CType(Me.Rows(0)(1), DateTime?)
            End Get
            Set(value As DateTime?)
                Me.Rows(0)(1) = value
            End Set
        End Property


        Protected Overrides Sub Initialize()

            Me.Columns.Add(New DataColumn("StartDate", GetType(DateTime?)))
            Me.Columns.Add(New DataColumn("EndDate", GetType(DateTime?)))

            Me.Rows.Add({Nothing, Nothing})

        End Sub

    End Class

End Namespace

还有:

Imports System
Imports System.Data
Imports System.Runtime.Serialization


Namespace SqlTypes

    <Serializable()> _
    <System.ComponentModel.DesignerCategory("Code")> _
    Public Class Paging
        Inherits SqlTableTypeBase

        Public Sub New()

            MyBase.New()

        End Sub


        Public Sub New(ByVal tableName As String)

            MyBase.New(tableName)

        End Sub


        Public Sub New(ByVal tableName As String, ByVal tableNamespace As String)

            MyBase.New(tableName, tableNamespace)

        End Sub


        Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)

            MyBase.New(info, context)

        End Sub


        'TODO: throw some more overloaded constructors in here...


        Public Sub New(pageNumber As Integer?, pageSize As Integer?)

            MyBase.New()

            Me.PageNumber = pageNumber
            Me.PageSize = pageSize

        End Sub


        Public Sub New(sortField As String, sortDirection As String)

            MyBase.New()

            Me.SortField = sortField
            Me.SortDirection = sortDirection

        End Sub


        Public Sub New(pageNumber As Integer?, pageSize As Integer?, sortField As String, sortDirection As String)

            Me.New(pageNumber, pageSize)

            Me.SortField = sortField
            Me.SortDirection = sortDirection

        End Sub


        Public Property PageNumber As Integer?
            Get
                Return CType(Me.Rows(0)(0), Integer?)
            End Get
            Set(value As Integer?)
                Me.Rows(0)(0) = value
            End Set
        End Property


        Public Property PageSize As Integer?
            Get
                Return CType(Me.Rows(0)(1), Integer?)
            End Get
            Set(value As Integer?)
                Me.Rows(0)(1) = value
            End Set
        End Property


        Public Property SortField As String
            Get
                Return CType(Me.Rows(0)(2), String)
            End Get
            Set(value As String)
                Me.Rows(0)(2) = value
            End Set
        End Property


        Public Property SortDirection As String
            Get
                Return CType(Me.Rows(0)(3), String)
            End Get
            Set(value As String)
                Me.Rows(0)(3) = value
            End Set
        End Property


        Protected Overrides Sub Initialize()

            Me.Columns.Add(New DataColumn("PageNumber", GetType(Integer?)))
            Me.Columns.Add(New DataColumn("PageSize", GetType(Integer?)))
            Me.Columns.Add(New DataColumn("SortField", GetType(String)))
            Me.Columns.Add(New DataColumn("SortDirection", GetType(String)))

            Me.Rows.Add({Nothing, Nothing, Nothing, Nothing})

        End Sub

    End Class

End Namespace

实例化对象并在构造函数中设置值,然后简单地从对象中获取参数,并将其附加到存储过程命令对象的参数集合中。

cmd.Parameters.Add(New DateRange(startDate, endDate).CreateParameter("DateRangeParams"))
cmd.Parameters.Add(New Paging(pageNumber, pageSize).CreateParameter("PagingParams"))

编辑 由于这个答案围绕着强类型,我想我应该在方法签名中添加一个强类型的例子:

'method signature with UDTs
Public Function GetMyReport(customParam1 as Integer, timeFrame as DateRange, pages as Paging) as IDataReader

'method signature without UDTs
Public Function GetMyReport(customParam1 as Integer, startDate as DateTime, endDate as DateTime, pageNumber as Integer, pageSize as Integer)

【讨论】:

  • 感谢您抽出宝贵时间发表评论并提供代码示例。虽然很多答案都围绕着 UDT,但您的答案却因 SQL 和 C# 示例而脱颖而出。
  • 我同意,如果我们确实需要这些参数,界面应该是这样的。
【解决方案2】:

我遇到了类似的情况,我发现 UDT 工作得非常完美。我们从一个非常相似的问题开始:“获取此帐户的数据”,然后变成“获取这些帐户的数据”,然后是“使用这些条件”等。我们使用 UDT 而不是传递 XML 字符串 - 一旦您进入 SP ,您可以直接从 UDT 中加入,并且 ADO.NET 支持 UDT,因此它非常简单。我们将数十万行从 UDT(大量 upsert)传递到我们的 SP,性能并没有成为问题,但有一个例外:当您发送这么多行时,永远不要尝试跟踪查询 - SQL server 内部的线程调度器会爆炸。

使用用户定义表类型时要注意的一点:出于某种原因,Microsoft 认为阻止您更改它们是个好主意,您只能删除/添加它们。然后其他人认为如果某些东西依赖于它们,最好阻止您丢弃它们,因此如果您手动更改它们,那么您最终会经历一个非常痛苦的过程来丢弃/重新组合它们。

我们没有将所有参数封装到单个 UDT 中,只是因为我们的需求因过程而异。因此,当我们有事物列表时,我们使用 UDT 作为该参数,但我可以很容易地看到一个 UDT 来统治所有事物是有用的,它具有一些方便的函数来提取众所周知的值,如日期。我鄙视多次编写相同的代码,这肯定会以较小的成本缩小您的代码库,从而增加复杂性。一个附带的好处是迫使所有开发人员坚持一种标准的做事方式,这在关键时间到来时并不总是强制执行。您还可以在数据层中为代码重用开辟一些不错的机会。

【讨论】:

  • 我喜欢您的回答 - 您确定了相同的模式并提出了类似的解决方案。您如何解决更新 U​​DT 的问题?如果我对您的理解正确,它们不能被更新,而是必须被删除/重新创建,这通过参考检查变得复杂。当您说“手动”时,您是指通过脚本还是使用 UI?
  • 因为 UDT 只能被删除/创建,不能更改所有依赖对象,例如在删除 UDT 之前,还必须删除使用 UDT 的存储过程/函数。没有简单的方法可以删除所有使用特定 UDT 的对象。它们需要手动识别。您可以查询信息架构以查找特定类型的参数,然后编写一个循环来删除那些包含这些参数的例程,但我不会称之为“简单”的解决方案。
  • 这正是我们所做的 - 设置它花了几个小时,不是太痛苦但不是微不足道的。您可以在 sys 模式中查找依赖关系,然后编写一个循环,获取 sys.all_sql_modules 中的依赖对象定义,并将定义写入表中。然后循环遍历、删除依赖项、删除/添加 UDT,然后重新构建依赖对象的定义。我没有这样做,也没有断言这是真的,但有人告诉我,像 dbProj 这样的一些工具会自动完成。
  • select distinct r.ROUTINE_TYPE, r.ROUTINE_SCHEMA, r.ROUTINE_NAME from INFORMATION_SCHEMA.PARAMETERS p inner join INFORMATION_SCHEMA.ROUTINES r on r.ROUTINE_NAME = p.SPECIFIC_NAME and r.ROUTINE_SCHEMA = p.SPECIFIC_SCHEMA where p .DATA_TYPE = '表类型' 和 p.USER_DEFINED_TYPE_NAME = 'MyUdtTypeName'
【解决方案3】:

我将使用 XML 作为参数并添加一些 UDF 来帮助解压缩您感兴趣的 XML 部分。标量值 UDF 用于单值参数,表值 UDF 用于列表。

在查询中嵌入 XML 往往会使查询优化器感到困惑,如果它以 where 子句或连接结尾,那么使用 UDF 的 可能 会成为性能杀手,因此我不会使用 XML 或UDF 在查询本身中。我首先将 XML 中的值获取到局部变量、表变量或临时表,然后在查询中使用这些值。

【讨论】:

  • 谢谢 - 您对在连接或 where 子句中使用 UDF 的担忧是正确的。对于我的实现,我会在使用 XML/filter 参数值之前将它们解压缩到单独的作用域变量中。我担心的一个问题是查询计划缓存或优化器因此失败,但我相信可以通过在 where 子句中使用之前解压缩到作用域变量来避免这种情况(类似于 DateTime 参数嗅探的问题/解决方案)
【解决方案4】:

在我看来,这个问题没有真正漂亮的解决方案。最大的问题是大多数情况下一些参数可以为空,但有些不是(不管参数是来自表值参数还是 XML 参数)。然后它以类似于以下的 SQL 结束:

Declare @Col1Value int = null
Declare @Col2Value int = null
Select * 
From dbo.MyTable
where (@Col1Value is Null Or Col1 = @Col1Value)
    And (@Col2Value is Null Or Col2 = @Col2Value)

当然,它的效率不高 + 查询计划目前还不是最好的..

动态 SQL 对解决问题有很大帮助。在这种情况下,虽然应该非常仔细地考虑用户权限(可以使用 Execute As someProxyUser、Certificates)。

然后可以使用一个输入 XML 参数创建过程,您可以在其中传递所需的所有参数,然后生成 SQL。但仍然不是很好的做事方式,因为当 SQL 变得更复杂时,涉及大量编码。例如,如果您从多个表中选择数据,并且其中多个表中有相同的列..

总而言之,我认为对于这个问题没有好的和优雅的解决方案。使用实体框架和传递参数的经典方式:)。

【讨论】:

  • 感谢您抽出宝贵时间回复。需要明确的是,我不希望将一个参数传递给我的所有程序,以代表整个 Where 子句。作为一个简单的示例,我只想封装所需的常用分页和日期过滤值(PageSize、PageNum、startdate、enddate)。几乎我们用于报告的所有查询都需要这些相同的参数,并且都包含您提到的样板类型。按照声明 @pagenum int;set @pagenum=isnull(@pagenumparam,1); ... 其中 1=1 并且 cte.PageNum = @pagenum;。我同意没有一个好的解决方案:)
【解决方案5】:

我们也遇到过这个问题。通过在数据库的可编程性/类型部分创建用户定义的表类型来解决。

user defined table types SQL Server 2008 R2

当调用不同的存储过程和函数时,该表在所有应用程序中使用。我们在 appl 客户端(vb.net 2010)以编程方式填写此表,然后将其作为参数传递。在存储过程中,我们只是读取表并执行我们需要做的任何事情,过滤、处理等。希望这会有所帮助。

【讨论】:

  • 感谢您的回答。我曾考虑过类似的事情,创建一个作为参数传递的 udt。您是如何在每个 SP 中实现“只需阅读表格并做我们需要做的事情”的模式的?我假设 udt 本质上会成为一个键/值存储,我仍然会在每个 SP 中得到很多样板
  • 对不起,在我完成我的想法之前提交的评论。你的 UDT 表结构是什么样的?您是否构建了任何 UDF 来提取值并减少样板文件?事后看来,根据您的经验,您将来会使用这种模式吗?
  • @mello702 不客气。首先,我尽量避免在 TSQL 中使用任何额外的进程,我的意思是,没有 UDF,没有动态参数等。有些情况是唯一的解决方案,但我相信不是这个。当我说只是读取表无非就是这样,从作为参数传递的表中执行一个普通的 SELECT 。不用说,这个 SELECT 可以像我想要的一样复杂,甚至可以使用 CTE、合并操作、调用另一个 SP 或函数或者我可能需要完成我的目标的其他任何东西。
  • 谢谢。除了非常特殊的情况,我也避免使用 UDT、UDF、触发器等。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-23
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 2018-04-29
  • 2011-09-19
  • 1970-01-01
相关资源
最近更新 更多