3

我们要求只使用存储过程来更新/插入 SQL 表中的数据。

我可以创建将更新 150 列的存储过程,但这将要求我需要从我拥有的对象中获取所有 150 列值并将所有 150 列显式传递给存储过程。

谁能建议我不必明确传递所有 150 个值的方法?

4

8 回答 8

6

这里的其他答案显示了实现目标的替代方法。但是,出于几个原因,我会提醒您不要使用它们。

  1. 大多数都很脆。这意味着您有潜在的问题不会显示编译时间,并且在许多情况下,除非每个边缘情况都经过正确测试,否则不会出现。

  2. 大多数会产生相同或更多的代码。在更新/插入主表之前,您不必通过解析字符串或将 xml 文件转换为临时表来保存任何代码。

  3. 非标准方式意味着该项目的下一个程序员将​​需要注意这个过程与其他过程的操作方式不同的事实,除了保存几行 C# 代码之外没有其他原因。

重点是,继续以通常的方式创建您的存储过程并以通常的方式调用它。大多数问题将出现在编译时或有限的测试用例中。此外,通过保持与其他代码相同的格式,您增加了未来更改成功的可能性,而无需大量额外工作。

于 2013-05-13T17:51:11.037 回答
3

你通过xml。

如果您为行/列提供值,则更新它。如果没有为您提供任何值,请使用原始值。这可以通过 CASE 语句来完成。

这是一个罗斯文的例子。

编辑:我在这里编写了一个#temp 表。您可以尝试使用 @variable 表。我有时都做过。它“取决于”。您可以直接从 xml 转到真实表(没有中间 #temp 或 @variable 表),一些原始的 MS 示例就是这样做的。您想尝试 MERGE(如果您有 2008 年及以上版本)或将 INSERT/UPDATE 语句包装在 TRAN 中。(我通常做的是先切碎 xml,然后开始 TRAN、插入/更新(或 MERGE),然后是 COMMIT。

Use Northwind
GO


IF OBJECT_ID('tempdb..#OrdersHolder') IS NOT NULL
begin
        drop table #OrdersHolder
end


CREATE TABLE #OrdersHolder
(
IdentityKey int not null identity (1001, 1), 
[OrderID] int, 
[ShippedDate] datetime,
[Freight] money
)


-- Declare XML variable

DECLARE @data XML;

-- Element-centered XML

SET @data = N'
<root>
    <Order>
        <OrderID>10248</OrderID>
        <ShippedDate>01/01/2001</ShippedDate>
        <Freight>33.33</Freight>
    </Order>

    <Order>
        <OrderID>10249</OrderID>
        <ShippedDate>02/02/2002</ShippedDate>
        <Freight>44.44</Freight>
    </Order>

    <Order>
        <OrderID>10250</OrderID>
        <Freight>55.55</Freight>
    </Order>

    <Order>
        <OrderID>10251</OrderID>
        <ShippedDate>09/09/2999</ShippedDate>
    </Order>

    <Order>
        <OrderID>-99999</OrderID>
        <ShippedDate>12/31/2222</ShippedDate>
        <Freight>333.00</Freight>
    </Order>

</root>

';



INSERT INTO #OrdersHolder ( [OrderID] ,  [ShippedDate] , [Freight] )
SELECT T.myEntity.value('(OrderID)[1]', 'INT') AS OrderID,
       T.myEntity.value('(ShippedDate)[1]', 'datetime') AS ShippedDate, 
       T.myEntity.value('(Freight)[1]', 'money') AS Freight
FROM @data.nodes('root/Order') AS T(myEntity)


;

select * from #OrdersHolder

/* Here is the magic */

Update dbo.[Orders] 
Set 
[ShippedDate] = CASE
                    WHEN holder.[ShippedDate] IS NOT NULL then holder.ShippedDate
                    ELSE realTable.ShippedDate
                END
,
[Freight] = CASE
                    WHEN holder.[Freight] IS NOT NULL then holder.Freight
                    ELSE realTable.Freight
                END
FROM    
    #OrdersHolder holder , dbo.[Orders] realTable
Where
    holder.OrderID = realTable.OrderID


/* Note, the OrderID will be incorrect because OrderID is IDENTITY , so I used -99999 to force a non match */
INSERT INTO dbo.[Orders] ( [ShippedDate] , [Freight] )
Select
[ShippedDate] = CASE
                    WHEN holder.[ShippedDate] IS NOT NULL then holder.ShippedDate
                    ELSE null
                END
,
[Freight] = CASE
                    WHEN holder.[Freight] IS NOT NULL then holder.Freight
                    ELSE null
                END
FROM    
    #OrdersHolder holder
Where
not exists ( select null from dbo.Orders innerRealTable where holder.OrderID = innerRealTable.OrderID )





/* Now show the real table data, 2 rows should have both [ShippedDate] and  [Freight] updated, 1 row just Freight and one row just the ShippedDate */

Select 
UpdateStatusFYI = 
CASE
    when realTable.OrderID = 10248 then 'Should be ShippedDate and Freight'
    when realTable.OrderID = 10249 then 'Should be ShippedDate and Freight'
    when realTable.OrderID = 10250 then 'Should be just Freight'
    when realTable.OrderID = 10251 then 'Should be ShippedDate'
    else 'unknown'
END
,
realTable.[OrderID], realTable.[ShippedDate] , realTable.[Freight]  from    #OrdersHolder holder join dbo.[Orders] realTable
    on holder.OrderID = realTable.OrderID 

/* Take a best shot at showing newly created data */
Select 
realTable.[OrderID], realTable.[ShippedDate] , realTable.[Freight]  from #OrdersHolder holder join dbo.[Orders] realTable
    on holder.ShippedDate = realTable.ShippedDate and holder.Freight = realTable.Freight 
Where
not exists ( select null from dbo.Orders innerRealTable where holder.OrderID = innerRealTable.OrderID )





IF OBJECT_ID('tempdb..#OrdersHolder') IS NOT NULL
begin
        drop table #OrdersHolder
end
于 2013-05-13T17:34:28.323 回答
1

很大程度上取决于基础表的要求,如果没有任何列可以为 Nullable,那么您可以像这样创建和 SP:

    CREATE PROCEDURE usp_SomeUpdateProc (@PrimaryKeyID INT,
     @Column1 VARCHAR = NULL,
     @Column2 INT = NULL,
     ...)
    AS 
    UPDATE dbo.SomeTable
    SET Column1 = ISNULL(@Column1, Column1),
        Column2 = ISNULL(@Column2, Column2),
        ....
    WHERE PrimaryKeyID = @PrimaryKeyId

使用这种类型的存储过程,只需要 @PrimaryKeyId 参数,您只需为正在更改的列的参数提供值。

于 2013-05-13T18:11:39.307 回答
1

不确定这是否是您想要的,但您可以做的一件事是将一个参数中的所有值传递给存储的过程(通过 VARCHAR(MAX) 或 TEXT 类型),并与一个字符连接,例如“|” (管道)然后在存储过程中将其拆分以更新每一列。这种方法有许多缺点(对于初学者来说,VARCHAR 的大小有限和参数的正确顺序),但如果没有进一步的细节,它可能是一个好的开始。

于 2013-05-13T17:23:34.777 回答
0

感谢您的回复,我使用的是实体框架 4,所以我使用了 Mapped the Stored proc。使用实体框架,然后创建方法,如 `public virtual ObjectResult> UpdateCaseFormID(Nullable id, string formId, Nullable usersId, string formDataSource, ....) {

        ObjectParameter idParameter;

        if (id.HasValue)
        {
            idParameter = new ObjectParameter("Id", id);
        }`

我使用 Object 将所有值显式传递给上述方法

于 2013-05-15T21:22:22.423 回答
0

你可以看看Dapper

它是一个“微 ORM”——它可以为您处理从对象属性复制到 DbCommands 参数的大量样板代码,而不会增加一些更大的 ORM 的膨胀。

于 2013-05-14T02:58:02.210 回答
0

将表值作为参数传递并使用MERGE语法。这会产生一个强类型、基于集合的解决方案。

SQL 2008 为我们提供了 TVP。通过这种方式,您可以像要更新的表一样格式化参数。然后,使用MERGE您只需将表中的所有列设置为 TVP 中的相应列。

TVP 是来自应用程序的强类型,不需要任何字符串解析或动态 SQL。

于 2013-05-13T18:09:01.720 回答
-1

在这种情况下,我宁愿使用数组(或复合变量)来传递值。但您需要注意此数组类型应保存所有 150 列数据类型的值。意味着您应该在这 150 个列数据类型中拥有主数据类型数组。

出色地 。在这种情况下,您可以使用数组或 varchar2 ..

于 2013-05-13T17:23:59.360 回答