在T-SQL中将单个字段值拆分为多个固定长度的列值
Split A Single Field Value Into Multiple Fixed-Length Column Values in T-SQL
我已经查看了关于 SO 的大约 15 个不同的答案,但还没有找到这种确切的情况。我正在进行自定义数据导出,需要导出到一个数据文件,该文件将导入到需要特定 length/format 中的数据的旧系统中。我有一个 "MEMO" 列,其中可以包含大量文本。我需要 select 该值并将其拆分为固定长度为 75 个字符的多个列。例如,如果我有一行包含 185 个字符的消息,我需要将其拆分为 3 个 75 个字符的新列,MEMO1、MEMO2、MEMO3,MEMO3 中剩余的 space 填充为 spaces 等于 75 个字符。另一个问题是,我最多只能使用 18 个 75 个字符的列将数据转储到其中。如果它超过 1350 (18x75) 个字符,其余的将被截断。
我试过这种方法,但它没有考虑新备忘录列的总数。我需要一些方法来迭代 NUMBEROFMEMOS 并且只需要 select 必要数量的新 MEMO 列,但显然你不能在 select.
中执行 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# 世界中,我只会创建一个方法来执行 for 循环直到 NUMBEROFMEMOS 并以这种方式输出数据。我认为这在这里行不通。提前致谢!
这是一个 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 Returns
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!
您可以使用动态 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;
确保所有结果字段的长度相同......
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
由于您是 .net
开发人员,我想您编写一个 .net
函数并可以在 T-SQL
代码中使用会很容易。为了编写 SQL CLR
函数检查 (我使用了 link 之一来实现 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
创建列并排除一些块。您现在可以在 table 中插入数据以具体化数据,然后将其导出。您可以使用上面的 link 实现这样的功能,或者创建您自己的功能来做您需要的事情。
如果将每一行数据划分为不同数量的列,则需要为每一行创建一个 INSERT
statament。
相反,您始终可以生成所有 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]
如果要导出的行很多,您可以分块工作
我已经查看了关于 SO 的大约 15 个不同的答案,但还没有找到这种确切的情况。我正在进行自定义数据导出,需要导出到一个数据文件,该文件将导入到需要特定 length/format 中的数据的旧系统中。我有一个 "MEMO" 列,其中可以包含大量文本。我需要 select 该值并将其拆分为固定长度为 75 个字符的多个列。例如,如果我有一行包含 185 个字符的消息,我需要将其拆分为 3 个 75 个字符的新列,MEMO1、MEMO2、MEMO3,MEMO3 中剩余的 space 填充为 spaces 等于 75 个字符。另一个问题是,我最多只能使用 18 个 75 个字符的列将数据转储到其中。如果它超过 1350 (18x75) 个字符,其余的将被截断。
我试过这种方法,但它没有考虑新备忘录列的总数。我需要一些方法来迭代 NUMBEROFMEMOS 并且只需要 select 必要数量的新 MEMO 列,但显然你不能在 select.
中执行 WHILESELECT 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# 世界中,我只会创建一个方法来执行 for 循环直到 NUMBEROFMEMOS 并以这种方式输出数据。我认为这在这里行不通。提前致谢!
这是一个 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 Returns
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!
您可以使用动态 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;
确保所有结果字段的长度相同......
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
由于您是 .net
开发人员,我想您编写一个 .net
函数并可以在 T-SQL
代码中使用会很容易。为了编写 SQL CLR
函数检查 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
创建列并排除一些块。您现在可以在 table 中插入数据以具体化数据,然后将其导出。您可以使用上面的 link 实现这样的功能,或者创建您自己的功能来做您需要的事情。
如果将每一行数据划分为不同数量的列,则需要为每一行创建一个 INSERT
statament。
相反,您始终可以生成所有 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]
如果要导出的行很多,您可以分块工作