SQL:将一行分成多行(规范化)

时间:2010-07-09 17:13:16

标签: sql sql-server sql-server-2000 normalization split-function

我正处于从设计糟糕的旧数据库升级到新数据库的过程中。在旧数据库中有tableA,其中包含字段Id和Commodities。 Id是主键,包含int,Commodities包含逗号分隔列表。

表A:

id   | commodities
1135 | fish,eggs,meat    
1127 | flour,oil  

在新数据库中,我希望tableB为表单id,商品,其中每个商品是tableA中逗号分隔列表中的单个项目。

表B:

id   | commodity
1135 | fish  
1135 | eggs   
1135 | meat  
1127 | flour  
1127 | oil    

我有一个函数functionA,当给定一个id,一个列表和一个分隔符时,返回一个带有id和item字段的表。如何使用此函数将tableA中的两个字段转换为tableB?

(注意:我无法弄清楚这个问题的标题。请随时编辑标题,以便更准确地反映这个问题!)

这是功能代码:

ALTER  FUNCTION dbo.functionA
(
@id int,
@List VARCHAR(6000),
@Delim varchar(5)
)
RETURNS
@ParsedList TABLE
(
id int, 
item VARCHAR(6000)
)
AS
BEGIN
DECLARE @item VARCHAR(6000), @Pos INT
SET @List = LTRIM(RTRIM(@List))+ @Delim
SET @Pos = CHARINDEX(@Delim, @List, 1)
WHILE @Pos > 0
BEGIN
SET @item = LTRIM(RTRIM(LEFT(@List, @Pos - 1)))
IF @item <> ''
BEGIN
INSERT INTO @ParsedList (id, item)
VALUES (@id, CAST(@item AS VARCHAR(6000)))
END
SET @List = RIGHT(@List, LEN(@List) - @Pos)
SET @Pos = CHARINDEX(@Delim, @List, 1)
END
RETURN
END

6 个答案:

答案 0 :(得分:3)

您需要一种在TSQL中拆分和处理字符串的方法,有很多方法可以做到这一点。本文涵盖几乎所有方法的PRO和CON:

Arrays and Lists in SQL Server 2000 and Earlier

您需要创建拆分功能。这就是如何使用拆分功能:

SELECT
    *
    FROM YourTable                               y
    INNER JOIN dbo.yourSplitFunction(@Parameter) s ON y.ID=s.Value

[我更喜欢使用数字表方法在TSQL中拆分字符串](Arrays and Lists in SQL Server 2000 and Earlier)但是有很多方法可以在SQL Server中拆分字符串,请参阅前面的链接,它解释了每个链接的PRO和CON。

要使Numbers Table方法起作用,您需要进行一次性表设置,这将创建一个包含1到10,000行的表Numbers

SELECT TOP 10000 IDENTITY(int,1,1) AS Number
    INTO Numbers
    FROM sys.objects s1
    CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)

设置Numbers表后,创建此拆分功能:

CREATE FUNCTION inline_split_me (@SplitOn char(1),@param varchar(7998)) RETURNS TABLE AS
   RETURN(SELECT substring(@SplitOn + @param + ',', Number + 1,
                    charindex(@SplitOn, @SplitOn + @param + @SplitOn, Number + 1) - Number - 1)
                 AS Value
          FROM   Numbers
          WHERE  Number <= len(@SplitOn + @param + @SplitOn) - 1
            AND  substring(@SplitOn + @param + @SplitOn, Number, 1) = @SplitOn)

GO 

您现在可以轻松地将CSV字符串拆分为表格并加入其中:

select * from dbo.inline_split_me(';','1;22;333;4444;;') where LEN(Value)>0

输出:

Value
----------------------
1
22
333
4444

(4 row(s) affected)

让你新表使用它:

--set up tables:
create table TableA (id int, commodities varchar(8000))
INSERT TableA VALUES (1135,'fish,eggs,meat')
INSERT TableA VALUES (1127,'flour,oil')

Create table TableB (id int, commodities varchar(8000))

--populate TableB
INSERT TableB
    (id, commodities)
SELECT
    a.id,c.value
    FROM TableA    a
        CROSS APPLY dbo.inline_split_me(',',a.commodities) c

 --show tableB contents:
select * from TableB

输出:

id          commodities
----------- -------------
1135        fish
1135        eggs
1135        meat
1127        flour
1127        oil

(5 row(s) affected)
在Conrad Frix关于SQL Server 2000不支持CROSS APPLY

的评论之后

编辑

这将做同样的事情:

INSERT TableB
        (id, commodities)
    SELECT 
        a.id,NullIf(SubString(',' + a.commodities + ',' , number , CharIndex(',' , ',' + a.commodities + ',' , number) - number) , '')
        FROM TableA            a
            INNER JOIN Numbers n ON 1=1
        WHERE SubString(',' + a.commodities + ',' , number - 1, 1) = ',' 
        AND CharIndex(',' , ',' + a.commodities + ',' , number) - number > 0
        AND number <= Len(',' + a.commodities + ',') 

并基于the link in the answer by @Rup的代码。它基本上删除了函数调用并在主查询中进行了拆分(使用类似的Numbers表拆分),因此不需要CROSS APPLY

答案 1 :(得分:3)

答案 2 :(得分:2)

编写一个循环遍历表A的SQL批处理,并将函数调用的结果插入表b中。

答案 3 :(得分:2)

叫我懒惰,但是我将合并后的行拉出数据库,拆分它们,然后重新插入拆分行。对于SQL来说,这种事情似乎有些不自然......

答案 4 :(得分:1)

如果可以使用,SSIS有一个非常方便的Unpivot变换。

答案 5 :(得分:1)

create table Project (ProjectId int, Description varchar(50));
insert into Project values (1, 'Chase tail, change directions');
insert into Project values (2, 'ping-pong ball in clothes dryer');

create table ProjectResource (ProjectId int, ResourceId int, Name varchar(15));
insert into ProjectResource values (1, 1, 'Adam');
insert into ProjectResource values (1, 2, 'Kerry');
insert into ProjectResource values (1, 3, 'Tom');
insert into ProjectResource values (2, 4, 'David');
insert into ProjectResource values (2, 5, 'Jeff');

-- a bit of SQL magic involving XML and voila
SELECT *, 
   (SELECT Name + ' ' AS [text()] 
    FROM ProjectResource pr 
    WHERE pr.ProjectId = p.ProjectId
    FOR XML PATH ('')) AS ResourceList 
FROM Project p

输出结果如下:

ProjectId   Description                       ResourceList
1           Chase tail, change directions     Adam Kerry Tom 
2           ping-pong ball in clothes dryer   David Jeff