【问题标题】:How to reuse code in SQL stored procedures?如何在 SQL 存储过程中重用代码?
【发布时间】:2009-07-14 15:47:04
【问题描述】:

我们使用 SQL Server 2005。我们所有的数据访问都是通过存储过程完成的。我们的选择存储过程总是返回多个结果集。

例如:

CREATE PROCEDURE hd_invoice_select(@id INT) AS
    SELECT * FROM Invoice WHERE InvoiceID = @id
    SELECT * FROM InvoiceItem WHERE InvoiceID = @id
    SELECT * FROM InvoiceComments WHERE InvoiceID = @id
    RETURN

我们应用程序的数据访问层基于结果(O/R Mapper 样式)构建对象图。

我遇到的问题是我们有许多不同的发票选择存储过程。它们都返回相同的结构,只是针对不同的选择标准。例如,我也有:

CREATE PROCEDURE hd_invoice_selectAllForCustomer(@customerID INT) AS
    SELECT * FROM Invoice WHERE CustomerID = @customerID
    SELECT * FROM InvoiceItem WHERE InvoiceID IN 
        (SELECT InvoiceID FROM Invoice WHERE CustomerID = @customerID)
    SELECT * FROM InvoiceComments WHERE InvoiceID = @id
        (SELECT InvoiceID FROM Invoice WHERE CustomerID = @customerID)
    RETURN

我还有很多其他的,包括:

hd_invoice_selectActive()
hd_invoice_selectOverdue()
hd_invoice_selectForMonth(@year INT, @month INT)

我对很多概念(客户、员工等)都有相同的模式

我们最终会复制大量代码,维护起来非常困难。当一个概念的“结构”发生变化时,我们必须去修复所有的过程,这很容易出错。

所以我的问题是:在场景中重用代码的最佳方式是什么?

我们提出了一个使用临时表的解决方案。但它不是很优雅。我会让您分享您的想法,如有必要,我将在即将发布的帖子中发布我的解决方案的详细信息,让您的 cmets 采用这种方法。

谢谢

【问题讨论】:

    标签: sql sql-server code-reuse


    【解决方案1】:

    将此作为第二个答案发布,因为它是一种不同的方法。如果您使用的是 SQL Server 2008:

    CREATE TYPE InvoiceListTableType AS TABLE 
    (
        InvoiceId INT
    );
    GO
    
    CREATE PROCEDURE hd_invoice_selectFromTempTable
    (
        @InvoiceList InvoiceListTableType READONLY
    )
    AS
    BEGIN
        SELECT * FROM Invoice WHERE InvoiceID IN
            (SELECT InvoiceId FROM @InvoiceList)
    
        SELECT * FROM InvoiceItem WHERE InvoiceID IN 
            (SELECT InvoiceId FROM @InvoiceList)
    
        SELECT * FROM InvoiceComments WHERE InvoiceID IN
            (SELECT InvoiceId FROM @InvoiceList)
    
        RETURN
    END
    GO
    
    CREATE PROCEDURE hd_invoice_select(@id INT) AS
    BEGIN
        DECLARE @InvoiceList AS InvoiceListTableType;
    
        SELECT id AS ID 
            INTO @InvoiceList
    
        EXEC hd_invoice_selectFromTempTable(@InvoiceList)
        RETURN
    END
    GO
    
    CREATE PROCEDURE hd_invoice_selectAllForCustomer(@customerID INT) AS
    BEGIN
        DECLARE @InvoiceList AS InvoiceListTableType;
    
        SELECT invoiceID as ID
            INTO @InvoiceList
            FROM Invoice WHERE CustomerID = @customerID
    
        EXEC hd_invoice_selectFromTempTable(@InvoiceList)
        RETURN
    END
    GO
    
    CREATE PROCEDURE hd_invoice_selectAllActive AS
    BEGIN
        DECLARE @InvoiceList AS InvoiceListTableType;
    
        SELECT invoiceID as ID
            INTO @InvoiceList
            FROM Invoice WHERE Status = 10002
    
        EXEC hd_invoice_selectFromTempTable(@InvoiceList)
        RETURN
    END
    GO
    

    【讨论】:

    • 这很酷。你知道这是如何执行的吗?查询优化器做得好吗?在内部,是使用临时表还是真的将集合作为简单值传递。
    • 我还没有机会使用它们,但对它们进行了一些阅读。如果可能的话,我非常反对临时表,所以只要它表现良好,它似乎是一个更优雅的解决方案。
    • 一旦我们迁移到 SQL 2008,我将重构以使用这种方法。
    • 这种方法的一个缺点是,一旦在许多存储过程中使用了表类型,就很难改变它的结构
    【解决方案2】:

    这种特定场景的“最佳”方式是使用某种代码生成。想出某种约定并将其插入代码生成器。

    【讨论】:

      【解决方案3】:

      您是否尝试将超过 1 个查询参数类型放入主 proc 的参数列表中?我只写了 proc 来覆盖 Invoice 表,您需要为您的其他表扩展它。

      CREATE PROCEDURE hd_invoice_select
      (
          @id INT = NULL
          , @customerId INT = NULL
      ) AS
      BEGIN
          SELECT * 
              FROM Invoice 
              WHERE 
                  (
                      @id IS NULL
                      OR InvoiceID = @id
                  )
                  AND (
                      @customerId IS NULL
                      OR CustomerID = @customerId
                  )
          RETURN
      END
      

      这个过程可以通过发送@id 和@customerId 作为NULL 来调用,对于基于@id 的特定InvoiceID 和@customerId 作为NULL(或者只是把它放在一起),或者对于基于特定客户的特定客户@customerId 将 @id 保留为 NULL 或将其从查询中排除。

      您还应该查看视图和表值用户定义函数。您可以将它们放在您的 procs 中,以将一些逻辑从 procs 中包装起来,以便可以在一个地方共享和维护它们。在视图/函数中包含一些逻辑还可以让您在查询窗口中处理数据,就像它是一个表一样。

      【讨论】:

      • 克里斯,这与我们使用的解决方案基本相同,但略有不同。在 WHERE 子句中,我们将使用: CustomerID = ISNULL(@customerId, CustomerID) 这样做的原因是如果值为空,我们会返回所有项目。这样,如果我们调用不带参数的 EXEC hd_invoice_select(),我们可以获得完整的列表。这可能不是您想要的,但我们发现它非常有用。干杯! -埃里克
      • 我们的两种方法都应该产生相同的记录集。我玩过一些使用 ISNULL 而不是 OR 版本的游戏,但不记得为什么我最终选择了 OR。从理论上讲,它们应该(几乎)相同地优化。我将尝试我们的一些更大的 proc,看看我是否能在这两种方法中发现明显的性能差异。
      • 打算在最后一条评论中包含这一点:我非常感谢您的简洁性。
      • 感谢您的回答。我认为如果您的参数数量非常少(2 个或 3 个),这种方法可以正常工作。但是这样的程序有 15 个可选参数,很难使用和理解。此外,通过提供单个 proc,您隐藏了该过程的含义和意图。
      • 而且,如果你有很多数据,性能会很糟糕。
      【解决方案4】:

      我是第一个提出这个问题的人。我在这里回答我自己的问题,让您知道我使用的代码重用解决方案,并让您的 cmets 采用这种方法。如果这个答案得到很多赞成票,我会选择它作为最终答案。

      这种方法有效且易于使用。我不知道它是否对性能有影响,因为它严重依赖临时表。

      对于我的应用程序中的每个概念,我都有一个这样的 storec proc:

      CREATE PROCEDURE hd_invoice_selectFromTempTable AS
      
          /* Get the IDs from an existing #TempInvoiceIDs temporary table */
      
          SELECT * FROM Invoice WHERE InvoiceID IN
              (SELECT ID FROM #TempInvoiceIDs)
      
          SELECT * FROM InvoiceItem WHERE InvoiceID IN 
              (SELECT ID FROM #TempInvoiceIDs)
      
          SELECT * FROM InvoiceComments WHERE InvoiceID IN
              (SELECT ID FROM #TempInvoiceIDs)
      
          RETURN
      

      然后我根据需要创建尽可能多的选择存储过程:

      CREATE PROCEDURE hd_invoice_select(@id INT) AS
      
          /* Fill #TempInvoiceIDs with matching IDs */
          SELECT id AS ID INTO #TempInvoiceIDs
      
          EXEC hd_invoice_selectFromTempTable
          RETURN
      
      CREATE PROCEDURE hd_invoice_selectAllForCustomer(@customerID INT) AS
      
          /* Fill #TempInvoiceIDs with matching IDs */
          SELECT invoiceID as ID
          INTO #TempInvoiceIDs
          FROM Invoice WHERE CustomerID = @customerID
      
          EXEC hd_invoice_selectFromTempTable
          RETURN
      
      CREATE PROCEDURE hd_invoice_selectAllActive AS
      
          /* Fill #TempInvoiceIDs with matching IDs */
          SELECT invoiceID as ID
          INTO #TempInvoiceIDs
          FROM Invoice WHERE Status = 10002
      
          EXEC hd_invoice_selectFromTempTable
          RETURN
      

      您如何看待这种方法?这有点类似于 AlexKuznetsov 的回答,但我使用临时表而不是 BLOB 参数。

      【讨论】:

      • 当你 pr0cedures 使用临时表时,它们(过程)一直在重新编译,占用大量 CPU - 有时我们需要它,通常我们不需要。您还可以将 ID 列表作为逗号分隔列表(有点慢)或作为固定宽度字符串表示的串联(快得多)提交 - 这不需要重新编译。
      • 非常有用的评论,谢谢。与总执行时间相比,重新编译 proc 的时间是否重要?如果一个 proc 需要 2ms 来重新编译和 120ms 来执行,那么重新编译时间并不重要。对吗?
      • 我喜欢这种方法,因为它直接使用 SQL Server 的自然对象。
      • SQL Server 2008 现在支持存储过程的表值参数。我还没有使用它们,但它可能会帮助您避免使用临时表:msdn.microsoft.com/en-us/library/bb510489.aspx
      • 我使用表值参数发布了这个答案的差异。
      【解决方案5】:

      这是存储过程的主要问题之一,也是人们不喜欢它们的原因。

      我从来没有找到或看到解决它的方法。

      【讨论】:

        【解决方案6】:

        我已经开始将代码生成器生成的存储过程用于我的基本 CRUD。我将存储过程用于报告或复杂的 SQL 工作。

        我也有一个与您的问题无关的建议 - 在您的 SQL 语句中使用 EXISTS 子句,而不是使用 IN 子句。

        【讨论】:

        • 你能解释一下为什么在这种情况下你认为 EXISTS 比 IN 更好吗?
        • 使用 IN 代替 EXISTS 会导致子查询数据的表扫描。 EXISTS 可以更好地利用索引。 IN 还可以使查询在每次通过主查询时搜索子查询中的项目列表。
        【解决方案7】:

        我继承了一个以前使用临时表方法的应用程序,我同意它非常混乱。

        在那个项目中,我们能够通过将它们替换为包含我们需要的所需“对象”的视图来删除大量临时表,然后我们更新了存储过程以查询这些视图。

        也许这也适用于您的情况。

        【讨论】:

          【解决方案8】:

          在某些情况下,我使用 VIEWS 来重用“代码”。在过滤器、活动项目、过时的东西等情况下......

          【讨论】:

            【解决方案9】:

            也许您应该学习使用联接。您可以将三个表的基本连接放在一个视图中,然后使用处理不同参数的 sp 进行查询。此外,您通常不应在生产代码中使用 select *。只返回您在这种情况下实际需要的几列,您的整个系统将获得更好的性能。另外,当人们改变你的结构时,你不会有意想不到的结果。

            【讨论】:

            • @HLGEM:您将如何使用连接返回多个结果集?我需要发票、发票的所有项目以及发票的所有 cmets 作为 3 个结果集返回。
            【解决方案10】:

            我有时分两步做:

            我想出了一个 InvoiceID 列表。然后我用这个列表作为参数调用我的存储过程。

            在 2005 年,我们没有表值参数,因此我将我的 ID 列表打包在一个二进制 BLOB 中并将其提交给 SQL Server,如下所述:Arrays and Lists in SQL Server 2005

            您还可以将 ID 列表提交为逗号分隔列表(有点慢)或固定宽度字符串表示的串联(快得多)。

            【讨论】:

            • 使用这种技术,你将如何使 proc hd_invoice_selectForMonth(@year INT, @month INT) 可以找到匹配的 ID,然后将它们打包并将它们发送到 hd_invoice_selectFromIDs(@ids blob)?
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-05-25
            • 1970-01-01
            • 2011-08-03
            • 2021-11-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多