如何通过分隔符将值拆分为不同的行?

时间:2014-12-07 06:03:29

标签: sql-server tsql ssis sql-server-2014

我是新手并且遇到了一个问题,我搜索了这些论坛并发现我需要使用UDF定义的函数来拆分值,因为SQL Server没有内置函数来拆分基于a的值分隔符。基于一篇文章(http://www.codeproject.com/Articles/7938/SQL-User-Defined-Function-to-Parse-a-Delimited-Str),我能够实现以下内容,但从性能角度看它并不是很好。我有大约75000条记录,需要很长时间才能运行。

最终结果是我需要获取所有购买值(由|分隔)并将每个值与名称放在一个表中。我有时可能有4个由|分隔的值或3,2无。

有人可以提供一些指示或不同的解决方案吗?也许使用像AutoIT离线的解决方案进行拆分?或者在SSIS?

我正在使用SQL Server 2012和SSIS来加载数据。

帮助!

由于

戴夫

BEGIN SET NOCOUNT ON;

DECLARE @Staging_Table TABLE
(
 ACCTID INT IDENTITY(1,1),
 NAME VARCHAR(50),
 PURCHASES VARCHAR(255)
 )

INSERT INTO @Staging_Table (Name, Purchases)
VALUES ('John','Vanilla|Chocolate|Peach')

INSERT INTO @Staging_Table (Name, Purchases)
VALUES ('Jack','Chocolate|Vanilla')

INSERT INTO @Staging_Table (Name, Purchases)
VALUES ('Mary','Peach|Vanilla|Bean')

INSERT INTO @Staging_Table (Name, Purchases)
VALUES ('Peter','Vanilla|Peach')

INSERT INTO @Staging_Table (Name, Purchases)
VALUES ('Jane','Bean|Vanilla|Chocolate|Peach')

-- Get the number of rows in the looping table
 DECLARE @RowCount INT
 SET @RowCount = (SELECT COUNT(ACCTID) FROM @Staging_Table)



-- Declare an iterator
DECLARE @I INT
-- Initialize the iterator
 SET @I = 1


-- Loop through the rows of a table @myTable
WHILE (@I <= @RowCount)
BEGIN
    -- Declare variables to hold the data which we get after looping each record

     DECLARE @NAME VARCHAR(255), @PURCHASES VARCHAR(255)  

     -- Get the data from table and set to variables
     SELECT 
        @NAME = NAME, 
        @PURCHASES = PURCHASES
    FROM 
        @Staging_Table
    WHERE 
        ACCTID = @I

    -- Display the looped data
        SELECT 
            @I,
            @NAME,  
            t.txt_value
            FROM dbo.fn_ParseText2Table(@PURCHASES, '|') as t 

            SET @I = @I  + 1
     END



END

3 个答案:

答案 0 :(得分:1)

        DECLARE @Tally TABLE (N INT)
        DECLARE @i AS INT = 1
        WHILE @i != 1000
        BEGIN
            INSERT INTO @Tally (N)  VALUES (@i)
            SET @i = @i + 1
        END
        --------------------------------------------------------
        DECLARE @Staging_Table TABLE 
                    (ACCTID INT IDENTITY(1, 1)
                    ,NAME VARCHAR(50)
                    ,PURCHASES VARCHAR(255))
        INSERT INTO @Staging_Table (NAME,Purchases)
        VALUES ('John','Vanilla|Chocolate|Peach')
              ,('Jack','Chocolate|Vanilla')
              ,('Mary','Peach|Vanilla|Bean')
              ,('Peter','Vanilla|Peach')
              ,('Jane','Bean|Vanilla|Chocolate|Peach')
              ,('Jane','Bean Vanilla Chocolate Peach')

    ----------------------------------------------------------
    --1 variant:

        SELECT E.NAME,f3.Purch
        FROM @Staging_Table AS E
        INNER JOIN @Tally AS T ON SUBSTRING('|' + E.Purchases, T.N, 1) = '|' AND T.N < LEN(E.Purchases) + 1
        CROSS APPLY (SELECT string = SUBSTRING(' ' + E.Purchases + '|', T.N + 1, LEN(E.Purchases) + 1)) f1
        CROSS APPLY (SELECT p1 = CHARINDEX('|', string)) f2
        CROSS APPLY (SELECT Purch = SUBSTRING(E.Purchases, T.N, p1-1)) f3
        ORDER BY E.NAME
    ----------------------------------------------------------
    --2 variant:

        SELECT  E.NAME ,
        ( CASE WHEN CHARINDEX('|', S.string) > 0
               THEN LEFT(S.string, CHARINDEX('|', S.string) - 1)
               ELSE string
          END ) AS Purch
        FROM    @Staging_Table AS E
        INNER JOIN @Tally AS T ON SUBSTRING('|' + PURCHASES, T.N, 1) = '|'
                                  AND T.N <= LEN(PURCHASES)
        CROSS APPLY ( SELECT    String = ( CASE WHEN T.N = 1
                                                THEN ( CASE WHEN CHARINDEX('|',
                                                              E.PURCHASES) > 0
                                                            THEN LEFT(E.PURCHASES,
                                                              CHARINDEX('|',
                                                              E.PURCHASES) - 1)
                                                            ELSE E.PURCHASES
                                                       END )
                                                ELSE SUBSTRING(E.PURCHASES,
                                                              T.N, 1000)
                                           END )
                    ) S
        ORDER BY E.NAME

答案 1 :(得分:0)

存储这样的数据是不好的做法,但我会告诉你一个我发现有效的解决方案。理想情况下,您可以在SQL服务器之外执行此操作。这对我来说足够快。

CREATE TABLE #tempValues (
    name VARCHAR(100), 
    val VARCHAR(100)
);

创建一个临时表来保存数据。解决方案是左侧的递归函数,然后调用右侧的函数。

CREATE FUNCTION breakStringUp
    @nameTemp VARCHAR(100),
    @stringToSplit VARCHAR(100)
BEGIN
     IF FIND(@stringToSplit, '|') > 0
     BEGIN
        INSERT INTO #tempValues
        ( name, val)
        VALUES
        (@nameTemp, LEFT(@stringToSplit,
                             FIND(@stringToSplit, '|'));

         breakStringUp(@nameTemp,
                 RIGHT( /* Recurse into the function, The index is 
                                                 taken from the right side so invert it
                                                 LEN(@stringToSpit)-FIND(...) */)
                )
      END
      ELSE
      BEGIN
             INSERT INTO #tempValues ..............
      END

上面的代码有点复杂。我刚刚编写了伪代码,但它应该可以工作,具体取决于模式。

  DECLARE CURSOR FOR ........

在行上声明一个光标并迭代。在每个名称和相应的VARCHAR上调用该函数。

  SELECT * FROM #tempValues;

并清理临时表

DROP #tempValues;

答案 2 :(得分:0)

SQL Server在字符串操作方面很慢,因此任何SQL实现都会在性能方面变得非常糟糕。如果你真的速度快,你将不得不创建一个CLR函数 - 正确编写,它将击败这个特定任务的任何SQL,放下手。

另一种选择,在您的情况下可能是也可能不是可行的解决方案,是SSIS包。

与此同时,您可以尝试使用XML进行行拆分 - 但它可能比循环更糟糕(更不用说许多其他缺陷):

select t.ACCTID, t.NAME, p.c.value('./@v', 'varchar(50)') as [PurchaseItem]
from @Staging_Table t
    cross apply (
        select cast('<r><i v="'
            + replace(t.PURCHASES, '|', '"/><i v="')
            + '" /></r>' as xml
        ) as [XData]
    ) ca
    cross apply ca.XData.nodes('/r[1]/i') p(c);