Sql server 按分隔符拆分多个字段

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

我必须编写一个SP,它可以对我们的数据库执行部分更新,更改存储在PU表的记录中。值字段包含由固定分隔符分隔的所有值。tables字段指的是一个Schemes表,其中包含Colums fiels中以类似方式表示的每个表的列名

现在,对于我的SP,我需要使用列/值对拆分临时表中的值字段和列字段,PU表中的每条记录都会发生这种情况

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([表],[列