6

我们使用 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)

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

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

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

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

谢谢

4

10 回答 10

2

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

于 2009-07-14T16:01:20.140 回答
2

将此作为第二个答案发布,因为它是一种不同的方法。如果您使用的是 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
于 2009-07-28T14:33:54.680 回答
1

您是否尝试在主 proc 的参数列表中放置超过 1 个查询参数类型?我只编写了 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 且@customerId 为NULL 的特定InvoiceID(或将其全部关闭)或基于@customerId 离开的特定客户,可以调用该过程。 @id 为 NULL 或将其从查询中排除。

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

于 2009-07-14T16:22:46.757 回答
1

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

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

对于我的应用程序中的每个概念,我都有一个这样的 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 参数。

于 2009-07-15T13:38:55.093 回答
0

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

我从来没有找到或看到绕过它的方法。

于 2009-07-14T15:56:30.680 回答
0

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

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

于 2009-07-14T16:01:00.780 回答
0

我有时分两步做:

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

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

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

于 2009-07-14T16:05:58.023 回答
0

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

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

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

于 2009-07-14T16:09:09.500 回答
0

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

于 2009-07-14T16:30:10.143 回答
0

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

于 2009-07-14T17:20:32.647 回答