【问题标题】:Split A Single Field Value Into Multiple Fixed-Length Column Values in T-SQL在 T-SQL 中将单个字段值拆分为多个固定长度的列值
【发布时间】:2016-06-28 14:32:24
【问题描述】:

我已经查看了大约 15 个关于 SO 的不同答案,但还没有找到这种确切的情况。我正在进行自定义数据导出,需要导出到数据文件,该文件将导入到需要特定长度/格式的数据的旧系统中。我有一个“备忘录”列,其中可以包含大量文本。我需要选择该值并将其拆分为多个固定长度为 75 个字符的列。例如,如果我有一行包含 185 个字符的消息,我需要将其拆分为 3 个 75 个字符的新列,MEMO1、MEMO2、MEMO3,MEMO3 中的剩余空间被填充为等于 75 个字符的空格.另一个问题是,我最多只能使用 18 个 75 字符的列将数据转储到其中。如果它长于 1350 (18x75) 个字符,则其余部分将被截断。

我尝试了这种方法,但它没有考虑新备忘录列的总数。我需要一些方法来迭代 NUMBEROFMEMOS 并只选择必要数量的新 MEMO 列,但显然你不能在选择中执行 WHILE。

SELECT FIRSTNAME, 
       LASTNAME, 
       DOB, 
       CEILING(LEN(NOTETEXT) / 75.0) as NUMBEROFMEMOS,
       SUBSTRING(NOTETEXT, 1, 75) as MEMOLINE1,
       SUBSTRING(NOTETEXT, 76, 149) as MEMOLINE2,
       SUBSTRING(NOTETEXT, 150, 224) as MEMOLINE3,
       etc. etc. etc
FROM CUSTOMER

我是一名长期的应用程序开发人员,正在尝试更多地参与数据库方面的工作。如果我在 C# 世界中,我会创建一个方法来执行最多 NUMBEROFMEMOS 的 for 循环并以这种方式输出数据。我认为这在这里行不通。提前致谢!

【问题讨论】:

  • 如果您正在考虑编写一个没有预定义列数的查询,那么唯一的方法就是使用动态 SQL。
  • 谢谢 Tab,我会调查的。
  • 与问题无关,请尝试使用_ 分隔变量中的单词或小写/大写,以便于阅读。 NOTETEXT 可以是 NOTE_TEXTnoteText。我发誓我想弄清楚NoTetext 是什么
  • 谢谢胡安,我也会记住这一点
  • 您可以编写一个存储过程,您可以在其中循环访问您的记录,就像在 c# 中一样。在那里,您可以创建一个具有足够 75 个长度列的临时表,并将其填充到循环中,最后只返回该临时表

标签: sql sql-server tsql ssms


【解决方案1】:

由于您是.net 开发人员,我想您编写一个可以在T-SQL 代码中使用的.net 函数会很容易。为了编写SQL CLR函数检查this answer(我使用了其中一个链接来实现SQL CLR正则表达式函数。


假设您需要将值分成 4 个长度的块并显示最多 6 个:

DECLARE @DataSouce TABLE
(
    [RecordID] TINYINT IDENTITY(1,1) PRIMARY KEY
   ,[RecordData] NVARCHAR(MAX)
);

INSERT INTO @DataSouce ([RecordData])
VALUES ('test some test goes here')
      ,('some numbers go here - 1111122222233333344444444445');


SELECT DS.[RecordID]
      ,RM.[MatchID]
      ,RM.[CaptureValue]
FROM @DataSouce DS
CROSS APPLY [dbo].[fn_Utils_RegexMatches] ([RecordData], '.{1,4}') RM;

现在数据被拆分。让我们 pivot 它并只显示其中的 6 个块:

SELECT *
FROM
(
    SELECT DS.[RecordID]
          ,RM.[MatchID]
          ,RM.[CaptureValue]
    FROM @DataSouce DS
    CROSS APPLY [dbo].[fn_Utils_RegexMatches] ([RecordData], '.{1,4}') RM
) DS
PIVOT
(
    MAX([CaptureValue]) FOR [MatchID] IN ([0], [1], [2], [3], [4], [5], [6])
) PVT;

这里我使用regex 函数来拆分数据和PIVOT 来创建列并排除一些块。您现在可以在表中插入数据以将其具体化,然后将其导出。您可以使用上面的链接实现此类功能,也可以创建自己的功能来做您需要的事情。

【讨论】:

    【解决方案2】:

    您可以使用动态 SQL。这是一个可以用来解决问题的示例:

    declare @text nvarchar(max) = N'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
    declare @len int = 75;
    
    declare @NUMBEROFMEMOS int = CEILING(LEN(@text) / @len);
    
    declare @query nvarchar(max) = N'select ';
    
    declare @loop int = 0;
    declare @start int = 1;
    declare @memoline int = 1;
    
    while @loop <= @NUMBEROFMEMOS begin
        if @loop > 0 begin
            set @query += N', ';
        end
    
       set @query += N'substring(''' + @text + N''', ' + cast(@start as nvarchar(max)) + N', ' + cast(@len as nvarchar(max)) + N') as MEMOLINE' + cast(@memoline as  nvarchar(max));
    
       set @start += @len
       set @loop += 1;
       set @memoline += 1;
    end
    
    execute sp_sqlexec @query;
    

    【讨论】:

      【解决方案3】:

      确保所有结果字段的长度相同......

      DECLARE @NoteText nVARCHAR(1350);
      DECLARE @newFieldLength int = 3;  --Yours wull be 75
      
      --GET ORIGINAL TEXT
      SET @NoteText = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; --SET FROM YOURS SOURCE TABLE I GUESS
      
      --MAKE SURE LAST ONE PADS TO THE REQUIRED LENGTH SO ALL FIELDS ARE THE SAME LENGTH
      DECLARE @mod int = @newFieldLength - ( LEN(@NoteText) % @newFieldLength );
      --SELECT @mod;
      WHILE @Mod > 0
      BEGIN
        SET @NoteText = @NoteText + ' ';
        SET @mod = @mod - 1;
      END
      
      DECLARE @NoOfFields INT;
      SELECT @NoOfFields = CEILING(LEN(@NoteText) / @newFieldLength ) + 1;
      
      DECLARE @Loop INT = 0;
      DECLARE @dynSQL nVarchar(MAX) = 'SELECT FIRSTNAME, LASTNAME, DOB, ' + CONVERT(nvarchar(4), @NoOfFields) + ' as NUMBEROFMEMOS, '; 
      DECLARE @pos INT = 1;
      WHILE @Loop < @NoOfFields
      BEGIN  
        IF @Loop > 0
        BEGIN
          SET @Pos = (@Loop * @newFieldLength) + 1;
        END;
        SET @dynSQL = @dynSQL + 'SUBSTRING(@NoteText, ' + CONVERT(nvarchar(2), @pos) + ', '  + CONVERT(nvarchar(2), @newFieldLength) +  ') as MEMOLINE_' + CONVERT(nvarchar(2), @loop + 1) + ', ';
      
        SET @Loop = @Loop + 1;
      END
      
      SET @dynSQL = @dynSQL + 'FROM CUSTOMER';
      SET @dynSQL = REPLACE( @dynSQL, ', FROM CUSTOMER', ' FROM CUSTOMER ');
      
      --RUN TEH RESULTANT SQL
      EXEC @dynSQL
      

      【讨论】:

        【解决方案4】:

        这是一个可以标准化您的数据的 cte。从那里你可以转动

        Declare @Customer table (FirstName varchar(50),LastName varchar(50),DOB Date,Memo varchar(max))
        Insert into @Customer values 
        ('John','Doe'  ,'1964-07-29','I''ve looked at about 15 different answers on SO but haven''t found this exact situation yet. I''m doing a custom data export and need to export to a data file that will be imported into an older system that needs the data in a specific length/format. I have a "MEMO" column that can have a large amount of text in it. I need to select that value and split it into multiple columns with a FIXED length of 75 chars. For instance, if I have a row with a message that is 185 chars, I need to split that into 3 new columns of 75 chars, MEMO1, MEMO2, MEMO3, with the remaining space in MEMO3 being filled with spaces to equal the 75 chars. The other catch, I can only use up to 18 75-char columns to dump the data into. If it''s longer than 1350 (18x75) chars, the rest gets truncated.'),
        ('Jane','Smith','1972-03-21','I''m a long-time application dev who is trying to get more involved in the DB side of things. If I were in the C# world, I would just create a method to do a for loop up to NUMBEROFMEMOS and output the data that way. I don''t think that works here though. Thanks in advance!')
        
        Declare @MaxLen int = 75
        
        ;with cteBase as (
            Select FirstName,LastName,DOB,Row=1,Memo=substring(Memo,1,@MaxLen) from @Customer
            Union All
            Select h.FirstName,h.LastName,h.DOB,Row=cteBase.Row+1,Memo=substring(h.Memo,((cteBase.Row+0)*@MaxLen)+1,@MaxLen) FROM @Customer h INNER JOIN cteBase ON h.FirstName = cteBase.FirstName and h.LastName = cteBase.LastName where substring(h.Memo,((cteBase.Row+0)*@MaxLen)+1,@MaxLen)<>''
        )
        --Select * from cteBase Order by LastName,Row
        Select FirstName,LastName,DOB
              ,Memo01=max(case when Row=1 then Memo else null end)
              ,Memo02=max(case when Row=2 then Memo else null end)
              ,Memo03=max(case when Row=3 then Memo else null end)
              ,Memo04=max(case when Row=4 then Memo else null end)
              ,Memo05=max(case when Row=5 then Memo else null end)
              ,Memo06=max(case when Row=6 then Memo else null end)
              ,Memo07=max(case when Row=7 then Memo else null end)
              ,Memo08=max(case when Row=8 then Memo else null end)
              ,Memo09=max(case when Row=9 then Memo else null end)
              ,Memo10=max(case when Row=10 then Memo else null end)
              ,Memo11=max(case when Row=11 then Memo else null end)
              ,Memo12=max(case when Row=12 then Memo else null end)
              ,Memo13=max(case when Row=13 then Memo else null end)
              ,Memo14=max(case when Row=14 then Memo else null end)
              ,Memo15=max(case when Row=15 then Memo else null end)
              ,Memo16=max(case when Row=16 then Memo else null end)
              ,Memo17=max(case when Row=17 then Memo else null end)
              ,Memo18=max(case when Row=18 then Memo else null end)
          from cteBase
          Group By FirstName,LastName,DOB
         Order by LastName
        

        CTE 回归

        FirstName   LastName    DOB         Row Memo
        John        Doe         1964-07-29  1   I've looked at about 15 different answers on SO but haven't found this exac
        John        Doe         1964-07-29  2   t situation yet. I'm doing a custom data export and need to export to a dat
        John        Doe         1964-07-29  3   a file that will be imported into an older system that needs the data in a 
        John        Doe         1964-07-29  4   specific length/format. I have a "MEMO" column that can have a large amount
        John        Doe         1964-07-29  5    of text in it. I need to select that value and split it into multiple colu
        John        Doe         1964-07-29  6   mns with a FIXED length of 75 chars. For instance, if I have a row with a m
        John        Doe         1964-07-29  7   essage that is 185 chars, I need to split that into 3 new columns of 75 cha
        John        Doe         1964-07-29  8   rs, MEMO1, MEMO2, MEMO3, with the remaining space in MEMO3 being filled wit
        John        Doe         1964-07-29  9   h spaces to equal the 75 chars. The other catch, I can only use up to 18 75
        John        Doe         1964-07-29  10  -char columns to dump the data into. If it's longer than 1350 (18x75) chars
        John        Doe         1964-07-29  11  , the rest gets truncated.
        Jane        Smith       1972-03-21  1   I'm a long-time application dev who is trying to get more involved in the D
        Jane        Smith       1972-03-21  2   B side of things. If I were in the C# world, I would just create a method t
        Jane        Smith       1972-03-21  3   o do a for loop up to NUMBEROFMEMOS and output the data that way. I don't t
        Jane        Smith       1972-03-21  4   hink that works here though. Thanks in advance!
        

        【讨论】:

          【解决方案5】:

          如果您将每一行数据划分为不同数量的列,则需要为每一行创建一个INSERT 状态。
          相反,您始终可以生成所有 18 个备忘录列并进行批量插入

          INSERT INTO [OtherServer.OtherDB.user.table]
          SELECT FIRSTNAME, 
                 LASTNAME, 
                 DOB, 
                 LEFT(SUBSTRING(NOTETEXT, 1, 75) + SPACE(75), 75) as MEMOLINE1,
                 LEFT(SUBSTRING(NOTETEXT, 76, 150) + SPACE(75), 75) as MEMOLINE2,
                 LEFT(SUBSTRING(NOTETEXT, 151, 225) + SPACE(75), 75) as MEMOLINE3,
                 ...
                 LEFT(SUBSTRING(NOTETEXT, 1276, 1350) + SPACE(75), 75) as MEMOLINE18,
          FROM [myServer.myDB.myUser.CUSTOMER]
          

          如果有很多行要导出,你可以分块工作

          【讨论】:

            猜你喜欢
            • 2022-11-17
            • 2019-03-02
            • 1970-01-01
            • 1970-01-01
            • 2012-05-15
            • 2015-05-02
            • 1970-01-01
            • 2018-10-12
            • 2022-01-19
            相关资源
            最近更新 更多