SQL: разбить одну строку на несколько (нормализация) - PullRequest
3 голосов
/ 09 июля 2010

Я нахожусь в процессе перехода от плохо спроектированной устаревшей базы данных к новой. В старой базе данных есть таблица A с полями Id и Commodities. Идентификатор является первичным ключом и содержит int, а Commodities содержит список, разделенный запятыми.

Таблица A:

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

В новой базе данных я хочу, чтобы tableB была в форме id, товара, где каждый товар представляет собой отдельный элемент из списка, разделенного запятыми в таблице A.

Таблица B:

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

У меня есть функция functionA, которая при получении идентификатора, списка и разделителя возвращает таблицу с полем id и item. Как я могу использовать эту функцию, чтобы превратить два поля из таблицы A в таблицу B?

(Примечание. У меня возникли проблемы с определением названия этого вопроса. Пожалуйста, не стесняйтесь редактировать название, чтобы оно более точно отражало вопрос!)

Вот код функции:

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 ]

3 голосов
/ 09 июля 2010

Вот ссылка, которую я разместил в комментарии:

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

3 голосов
/ 09 июля 2010

Вам нужен способ разбить и обработать строку в TSQL, есть много способов сделать это. В этой статье рассматриваются плюсы и минусы практически каждого метода:

Массивы и списки в SQL Server 2000 и более ранних версиях

Вам необходимо создать функцию разделения. Вот как можно использовать функцию разделения:

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

[Я предпочитаю подход с использованием таблиц чисел для разделения строки в TSQL] ( Массивы и списки в SQL Server 2000 и более ранних версиях ), но существует множество способов разделения строк в SQL Server, см. Предыдущую ссылку , который объясняет за и против каждого.

Чтобы метод таблицы чисел сработал, необходимо выполнить настройку единой таблицы, которая создаст таблицу Numbers, содержащую строки от 1 до 10000:

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)

EDIT после комментария Конрада Фрикса о том, что 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 + ',') 

и основан на коде от ссылка в ответе @ Rup . Он в основном удаляет вызов функции и выполняет разделение в основном запросе (используя аналогичное разделение таблицы Numbers), поэтому нет необходимости в CROSS APPLY.

2 голосов
/ 09 июля 2010

Назовите меня ленивым, но я бы вытащил объединенные строки из базы данных, разделил их, а затем снова вставил разделенные строки.Подобные вещи кажутся неестественными для SQL ...

2 голосов
/ 09 июля 2010

Вы пишете пакет SQL, который проходит по таблице A и вставляет в таблицу b результаты вашего вызова функции.

1 голос
/ 19 апреля 2011
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 
1 голос
/ 09 июля 2010

В SSIS есть довольно удобное преобразование Unpivot, если оно вам доступно.

...