Sql server 按分隔符拆分多个字段
我必须编写一个SP,它可以对我们的数据库执行部分更新,更改存储在PU表的记录中。值字段包含由固定分隔符分隔的所有值。tables字段指的是一个Schemes表,其中包含Colums fiels中以类似方式表示的每个表的列名 现在,对于我的SP,我需要使用列/值对拆分临时表中的值字段和列字段,PU表中的每条记录都会发生这种情况Sql server 按分隔符拆分多个字段,sql-server,tsql,Sql Server,Tsql,我必须编写一个SP,它可以对我们的数据库执行部分更新,更改存储在PU表的记录中。值字段包含由固定分隔符分隔的所有值。tables字段指的是一个Schemes表,其中包含Colums fiels中以类似方式表示的每个表的列名 现在,对于我的SP,我需要使用列/值对拆分临时表中的值字段和列字段,PU表中的每条记录都会发生这种情况 alter table PU add RowOrder int identity not null 例如: 我们的PU表看起来像这样: CREATE TABLE [dbo
alter table PU add RowOrder int identity not null
例如:
我们的PU表看起来像这样:
CREATE TABLE [dbo].[PU](
[Table] [nvarchar](50) NOT NULL,
[Values] [nvarchar](max) NOT NULL
)
在此示例中插入SQL:
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','John Doe;26');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','Jane Doe;22');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','Mike Johnson;20');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','Mary Jane;24');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Course','Mathematics');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Course','English');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Course','Geography');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Campus','Campus A;Schools Road 1;Educationville');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Campus','Campus B;Schools Road 31;Educationville');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Campus','Campus C;Schools Road 22;Educationville');
INSERT INTO [dbo].[Schemes]([Table],[Columns]) VALUES ('Person','[Name];[Age]');
INSERT INTO [dbo].[Schemes]([Table],[Columns]) VALUES ('Course','[Name]');
INSERT INTO [dbo].[Schemes]([Table],[Columns]) VALUES ('Campus','[Name];[Address];[City]');
我们有一个类似的计划表:
CREATE TABLE [dbo].[Schemes](
[Table] [nvarchar](50) NOT NULL,
[Columns] [nvarchar](max) NOT NULL
)
在此示例中插入SQL:
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','John Doe;26');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','Jane Doe;22');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','Mike Johnson;20');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Person','Mary Jane;24');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Course','Mathematics');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Course','English');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Course','Geography');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Campus','Campus A;Schools Road 1;Educationville');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Campus','Campus B;Schools Road 31;Educationville');
INSERT INTO [dbo].[PU]([Table],[Values]) VALUES ('Campus','Campus C;Schools Road 22;Educationville');
INSERT INTO [dbo].[Schemes]([Table],[Columns]) VALUES ('Person','[Name];[Age]');
INSERT INTO [dbo].[Schemes]([Table],[Columns]) VALUES ('Course','[Name]');
INSERT INTO [dbo].[Schemes]([Table],[Columns]) VALUES ('Campus','[Name];[Address];[City]');
因此,PU表的第一条记录应生成一个临时表,如:
第五届会议将有:
最后,第八个PU记录应导致:
你明白了。
我尝试使用以下查询创建临时表,但遗憾的是,当PU记录中有多个值时,查询失败:
DECLARE @Fields TABLE
(
[Column] INT,
[Value] VARCHAR(MAX)
)
INSERT INTO @Fields
SELECT TOP 1
(SELECT Value FROM STRING_SPLIT([dbo].[Schemes].[Columns], ';')),
(SELECT Value FROM STRING_SPLIT([dbo].[PU].[Values], ';'))
FROM [dbo].[PU] INNER JOIN [dbo].[Schemes] ON [dbo].[PU].[Table] = [dbo].[Schemes].[Table]
TOP 1正确获取第一个PU记录,因为每个PU记录在处理后都会被删除
错误是:
子查询返回了多个值。当子查询在=、!=、=或者当子查询用作表达式时
在Person记录的情况下,分割实际上一次返回2个值/列,我只想将这些值存储在2个记录中,而不是得到一个错误
有关于重写上述查询的帮助吗
还要注意的是,这些数据只是泛泛而谈。问题的关键在于,能够有两个字段都具有分隔值,且金额始终相等(例如,PU表中的“person”在字段中始终具有两个分隔值),并将它们拆分为多个列/标题行
更新:工作执行
根据肖恩·兰格(Sean Lange)的(公认的)答案,我能够在实施过程中制定出以下解决问题的方法:
由于我需要重用它,合并列/值功能由一个新函数执行,声明如下:
CREATE FUNCTION [dbo].[JoinDelimitedColumnValue]
(@splitValues VARCHAR(8000), @splitColumns VARCHAR(8000),@pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH MyValues AS
(
SELECT ColumnPosition = x.ItemNumber,
ColumnValue = x.Item
FROM dbo.DelimitedSplit8K(@splitValues, @pDelimiter) x
)
, ColumnData AS
(
SELECT ColumnPosition = x.ItemNumber,
ColumnName = x.Item
FROM dbo.DelimitedSplit8K(@splitColumns, @pDelimiter) x
)
SELECT cd.ColumnName,
v.ColumnValue
FROM MyValues v
JOIN ColumnData cd ON cd.ColumnPosition = v.ColumnPosition
;
对于上述示例数据,我将使用以下SQL调用此函数:
DECLARE @FieldValues VARCHAR(8000), @FieldColumns VARCHAR(8000)
SELECT TOP 1 @FieldValues=[dbo].[PU].[Values], @FieldColumns=[dbo].[Schemes].[Columns] FROM [dbo].[PU] INNER JOIN [dbo].[Schemes] ON [dbo].[PU].[Table] = [dbo].[Schemes].[Table]
INSERT INTO @Fields
SELECT [Column] = x.[ColumnName],[Value] = x.[ColumnValue] FROM [dbo].[JoinDelimitedColumnValue](@FieldValues, @FieldColumns, @Delimiter) x
这种数据结构使得这种方式比它应该的更加复杂。您可以在这里利用Jeff Moden的拆分器。该拆分器与所有其他拆分器的主要区别在于,它返回每个元素的顺序位置。我不明白为什么其他拆分器都不这么做。像这样的事情是需要的。您有两组分隔数据,必须确保它们都按正确的顺序重新组装 我看到的最大问题是,主表中没有任何内容可以作为正确排序结果的锚。您需要一些东西,甚至是一个标识来确保输出行“在一起”。为了实现这一点,我只是在PU表中添加了一个标识
alter table PU add RowOrder int identity not null
现在我们有了一个锚,对于一个简单的查询来说,这仍然有点麻烦,但这是可以实现的
像这样的东西现在可以用了
with MyValues as
(
select p.[Table]
, ColumnPosition = x.ItemNumber
, ColumnValue = x.Item
, RowOrder
from PU p
cross apply dbo.DelimitedSplit8K(p.[Values], ';') x
)
, ColumnData as
(
select ColumnName = replace(replace(x.Item, ']', ''), '[', '')
, ColumnPosition = x.ItemNumber
, s.[Table]
from Schemes s
cross apply dbo.DelimitedSplit8K(s.Columns, ';') x
)
select cd.[Table]
, v.ColumnValue
, cd.ColumnName
from MyValues v
join ColumnData cd on cd.[Table] = v.[Table]
and cd.ColumnPosition = v.ColumnPosition
order by v.RowOrder
, v.ColumnPosition
我首先建议不要存储这样的值。我建议在表中有一个键值,最好不要将表和列用作复合键。我建议避免使用保留字。我也不知道你使用的是什么版本的SQL。我将假设您使用的是Microsoft SQL Server的最新版本,该版本将支持我提供的存储过程 以下是解决方案的概述: 1) 您需要将PU表和Schema表转换为一个表,在该表中,列列表中的每个“列”值都在各自的行中隔离。如果您能以这种格式而不是提供的格式存储数据,您的情况会好一点 我的意思是
Table|Columns
Person|Jane Doe;22
需要转换为
Table|Column|OrderInList
Person|Jane Doe|1
Person|22|2
有多种方法可以做到这一点,但我更喜欢我学会的xml技巧。你可以在网上找到多个拆分字符串的例子,所以我不会集中讨论这个问题。使用任何能给你最好表现的东西。不幸的是,您可能无法摆脱这个表值函数
更新:
多亏了Shnugo的性能增强评论,我更新了xml拆分器,为您提供了行数,从而减少了一些代码。我对模式列表执行完全相同的操作
2) 由于新模式表和新PU表现在具有每列显示的顺序,因此PU表和模式表可以在“表”和OrderInList上联接
CREATE FUNCTION [dbo].[fnSplitStrings_XML]
(
@List NVARCHAR(MAX),
@Delimiter VARCHAR(255)
)
RETURNS TABLE
AS
RETURN
(
SELECT y.i.value('(./text())[1]', 'nvarchar(4000)') AS Item,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as RowNumber
FROM
(
SELECT CONVERT(XML, '<i>'
+ REPLACE(@List, @Delimiter, '</i><i>')
+ '</i>').query('.') AS x
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
CREATE Procedure uspGetColumnValues
as
Begin
--Split each value in PU
select p.[Table],p.[Values],a.[Item],CHARINDEX(a.Item,p.[Values]) as LocationInStringForSorting,a.RowNumber
into #PuWithOrder
from PU p
cross apply [fnSplitStrings_XML](p.[Values],';') a --use whatever string split function is working best for you (performance wise)
--Split each value in Schema
select s.[Table],s.[Columns],a.[Item],CHARINDEX(a.Item,s.[Columns]) as LocationInStringForSorting,a.RowNumber
into #SchemaWithOrder
from Schemes s
cross apply [fnSplitStrings_XML](s.[Columns],';') a --use whatever string split function is working best for you (performance wise)
DECLARE @Fields TABLE --If this is an ETL process, maybe make this a permanent table with an auto incrementing Id and reference this table in all steps after this.
(
[Table] NVARCHAR(50),
[Columns] NVARCHAR(MAX),
[Column] VARCHAR(MAX),
[Value] VARCHAR(MAX),
OrderInList int
)
INSERT INTO @Fields([Table],[Columns],[Column],[Value],OrderInList)
Select pu.[Table],pu.[Values] as [Columns],s.Item as [Column],pu.Item as [Value],pu.RowNumber
from #PuWithOrder pu
join #SchemaWithOrder s on pu.[Table]=s.[Table] and pu.RowNumber=s.RowNumber
Select [Table],[Columns],[Column],[Value],OrderInList
from @Fields
order by [Table],[Columns],OrderInList
END
GO
EXEC uspGetColumnValues
GO
CREATE FUNCTION[dbo].[fnSplitStrings\u XML]
(
@列出NVARCHAR(最大值),
@分隔符VARCHAR(255)
)
返回表
作为
返回
(
选择y.i.value(“(./text())[1]”,选择“nvarchar(4000)”作为项目,选择(ORDER BY(SELECT NULL))上方的行号()作为行号
从…起
(
选择“转换(XML)”
+替换(@List、@Delimiter、)
+'')。查询('.')为x
)交叉应用x.nodes('i')作为y(i)
);
去
创建过程uspGetColumnValues
作为
开始
--将每个值拆分为PU
选择p.[Table]、p.[Values]、a.[Item]、CHARINDEX(a.Item,p.[Values])作为LocationInStringForSorting,a.RowNumber
变得井井有条
来自普普
交叉应用[fnSplitStrings_XML](p.[Values],“;”)a——使用最适合您的字符串拆分函数(性能方面)
--在模式中拆分每个值
选择s.[表格]、s.[列]、a.[项目]、CHARINDEX(a.Item,s.[列])作为位置InstringForSorting,a.RowNumber
进入有序的计划
从计划中
交叉应用[fnSplitStrings_XML](s.[Columns],“;”)a——使用最适合您的字符串拆分函数(性能方面)
DECLARE@Fields TABLE——如果这是一个ETL过程,可以将其设置为具有自动递增Id的永久表,并在此后的所有步骤中引用该表。
(
[表]NVARCHAR(50),
[列]NVARCHAR(最大值),
[列]VARCHAR(最大值),
[值]VARCHAR(最大值),
命令列表int
)
插入@Fields([表],[列