将多行数据传递给存储过程

时间:2012-06-11 19:14:43

标签: c# sql-server-2005

我有一个C#.net中的对象列表(由多个文本文件创建),我需要将它存储在SQL2005数据库文件中。不幸的是,表值参数从SQL2008开始,因此它们无济于事。我在MSDN中发现,一种方法是“将多个数据值捆绑成分隔的字符串或XML文档,然后将这些文本值传递给过程或语句”,但我对存储过程很新,需要更多的帮助。我知道我可以创建一个存储过程来创建一个记录然后遍历我的列表并添加它们,但这就是我想要避免的。感谢。

Input file example (Other files contain pricing and availability):
Matnr   ShortDescription    LongDescription ManufPartNo Manufacturer    ManufacturerGlobalDescr GTIN    ProdFamilyID    ProdFamily  ProdClassID ProdClass   ProdSubClassID  ProdSubClass    ArticleCreationDate CNETavailable   CNETid  ListPrice   Weight  Length  Width   Heigth  NoReturn    MayRequireAuthorization EndUserInformation  FreightPolicyException
10000000    A&D ENGINEERING SMALL ADULT CUFF FOR UA-767PBT  UA-279  A&D ENGINEERING A&D ENG 093764011542    GENERAL General TDINTERNL   TD Internal TDINTERNL   TD Internal 2012-05-13 12:18:43 N       18.000  .350                N   N   N   N
10000001    A&D ENGINEERING MEDIUM ADULT CUFF FOR UA-767PBT UA-280  A&D ENGINEERING A&D ENG 093764046070    GENERAL General TDINTERNL   TD Internal TDINTERNL   TD Internal 2012-05-13 12:18:43 N       18.000  .450                N   N   N   N

一些DataBase文件字段:

EffectiveDate           varchar(50)
MfgName                 varchar(500)
MfgPartNbr              varchar(500)
Cost                    varchar(200)
QtyOnHand               varchar(200)

3 个答案:

答案 0 :(得分:2)

您可以非常轻松地从单个字符串中拆分多个值。假设你可以像这样捆绑字符串,用逗号分隔“列”,用分号分隔“行”:

foo, 20120101, 26; bar, 20120612, 32

(这假设冒号和分号不能自然地出现在数据中;如果可以,则需要选择其他分隔符。)

您可以构建这样的拆分例程,其中包含一个输出列,允许您确定值在原始字符串中出现的顺序:

CREATE FUNCTION dbo.SplitStrings
(
    @List       NVARCHAR(MAX),
    @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
AS
    RETURN (SELECT Number = ROW_NUMBER() OVER (ORDER BY Number),
        Item FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(@List, Number, 
        CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)))
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
        FROM sys.all_objects) AS n(Number)
    WHERE Number <= CONVERT(INT, LEN(@List))
        AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter
    ) AS y);
GO

然后你可以像这样查询它(为了简单和说明,我只处理3个属性,但你可以推断11或n):

DECLARE @x NVARCHAR(MAX); -- a parameter to your stored procedure

SET @x = N'foo, 20120101, 26; bar, 20120612, 32';

;WITH x AS 
(
    SELECT ID = s.Number, InnerID = y.Number, y.Item 
    -- parameter and "row" delimiter here:
    FROM dbo.SplitStrings(@x, ';') AS s
    -- output and "column" delimiter here:
    CROSS APPLY dbo.SplitStrings(s.Item, ',') AS y
)
SELECT 
    prop1 = x.Item, 
    prop2 = x2.Item, 
    prop3 = x3.Item
FROM x 
INNER JOIN x AS x2 
ON x.InnerID = x2.InnerID - 1
AND x.ID = x2.ID
INNER JOIN x AS x3
ON x2.InnerID = x3.InnerID - 1
AND x2.ID = x3.ID
WHERE x.InnerID = 1
ORDER BY x.ID;

结果:

prop1   prop2     prop3
------  --------  -------
foo     20120101  26
bar     20120612  32

答案 1 :(得分:0)

我们使用像这样的XML数据类型......

declare @contentXML xml
set @contentXML=convert(xml,N'<ROOT><V a="124694"/><V a="124699"/><V a="124701"/></ROOT>')

SELECT  content_id,
  FROM  dbo.table c WITH (nolock) 
  JOIN @contentXML.nodes('/ROOT/V') AS R ( v ) ON c.content_id = R.v.value('@a', 'INT')

以下是调用存储过程时的样子......

DbCommand dbCommand = database.GetStoredProcCommand("MyStroredProcedure);
database.AddInParameter(dbCommand, "dataPubXML", DbType.Xml, dataPublicationXml);

CREATE PROC dbo.usp_get_object_content
(
    @contentXML XML
)
AS 
BEGIN
    SET NOCOUNT ON

    SELECT  content_id,
      FROM    dbo.tblIVContent c WITH (nolock) 
      JOIN @contentXML.nodes('/ROOT/V') AS R ( v ) ON c.content_id = R.v.value('@a', 'INT')

 END

SQL Server不能非常快速地解析XML,因此使用SplitStrings函数可能会更高效。只是想提供另一种选择。

答案 2 :(得分:0)

我可以想到一些选项,但是当我输入其中一个选项时(Split选项)由@Bertrand先生发布。唯一的问题是SQL just isn't that good at string manipulation

因此,另一种选择是使用您的sproc假定存在的#Temp表。将动态SQL构建为以下效果:

使用您需要的形状启动事务CREATE TABLE #InsertData,然后使用INSERT INTO #InsertData SELECT <values> UNION ALL SELECT <values>....

循环您要插入的数据

这种方法存在一些限制,其中之一是随着数据集变得非常大,您可能需要将INSERT分成批处理。 (我不记得我自己学习这个时遇到的具体错误,但是对于很长时间的SQL列表我抱怨SQL。)但是,解决方案很简单:只生成一系列较小的INSERT每行数。例如,您可以执行10 INSERT SELECTs,每个UNION ALLs,而不是1 INSERT SELECT,只有10000 UNION ALLs。您仍然可以将整个批处理作为单个命令的一部分传递。

这个的优点(尽管有各种缺点 - 使用临时表,长命令字符串等)是它将所有字符串处理卸载到方程式更高效的C#方面并且不会需要一个额外的持久数据库对象(Split功能;但是,有时候谁也不需要其中一个)?

如果您使用Split()函数,我建议您将其卸载到SQLCLR函数,而不是T-SQL UDF(出于上面链接所示的性能原因)。

最后,无论您选择哪种方法,请注意,如果您的数据可以包含包含分隔符的字符串,那么您会遇到更多问题(例如,在Aaron的答案中,如果数据是:

'I pity the foo!', 20120101, 26; 'bar, I say, bar!', 20120612, 32

同样,因为C#在字符串处理方面比T-SQL更好,所以如果不使用T-SQL UDF来处理这个问题,你会更好。

修改

请注意以下附加点,以考虑动态INSERT选项。

您需要确定此处的输入是否是潜在的危险输入,并且需要在使用前进行清洁。您无法轻松地对此数据进行参数化,因此这是一个重要的数据。在我使用这个策略的地方,我已经对数据的类型有了很强的保证(特别是,我已经用它来播种一个带有整数ID列表的表来处理,所以我迭代整数而不是任意的,不受信任的字符串)。如果您没有类似的保证,请注意SQL注入的危险。