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

Posted

技术标签:

【中文标题】SQL:将一行分成多行(规范化)【英文标题】:SQL: break up one row into many (normalization) 【发布时间】:2011-03-13 23:59:25 【问题描述】:

我正在从设计不佳的旧数据库升级到新数据库。在旧数据库中,tableA 带有字段 Id 和 Commodities。 Id 是主键,包含一个 int,而 Commodities 包含一个逗号分隔的列表。

表A:

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

在新数据库中,我希望 tableB 采用 id,commercial 形式,其中每个商品都是 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

【问题讨论】:

你的意思通常被称为规范化(1. 准确的规范形式)。也许您想将此添加到您的标题中。 这个? sqlteam.com/article/parsing-csv-values-into-multiple-rows @Rup:将其发布为答案。 @Rup- 谢谢,谢谢,谢谢!请将其作为答案发布,以便我投票和接受。 好的,当然,完成。我不确定你的功能是什么,所以我不知道这是否回答了整个问题。 【参考方案1】:

这是我作为评论发布的链接:

http://www.sqlteam.com/article/parsing-csv-values-into-multiple-rows

【讨论】:

如果您是作者,为什么不复制/粘贴,以便堆垛机可以看到它以防下游发生某些事情?【参考方案2】:

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

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 中拆分字符串的方法有很多,请参阅上一个链接,其中解释了每种方法的优缺点。

要使 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

【讨论】:

@Conrad Frix,我已经使用包含 Numbers 表拆分的单个语句 INSERT 更新了我的答案,因此不需要 CROSS APPLY(当然也不需要循环),它基于the link in the answer by @Rup中的代码【参考方案3】:

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

【讨论】:

似乎应该有一个基于集合的方法来做到这一点......我讨厌唯一的解决方案是批处理......必须有另一种方式...... 好吧,根据那篇文章,您需要一个循环或那个计数表。所以我想只是使用批处理...基于集合的意识形态似乎在这里失败了... 这可以在没有循环的情况下完成,在单个INSERT 中,请参阅我的答案。【参考方案4】:

叫我懒惰,但我会从数据库中提取合并的行,拆分它们,然后重新插入拆分的行。这种事情对于 SQL 来说似乎有点不自然……

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:
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 

【讨论】:

以上是关于SQL:将一行分成多行(规范化)的主要内容,如果未能解决你的问题,请参考以下文章

sql 语句怎么将一行拆分成两行

如何在 BigQuery 中将多行聚合为一行?

sql一行拆分多行记录

python篇第3天编码规范

前端开发规范Javascript

根据多列将一行分成多行[重复]