2012-06-06 4 views
2

У меня есть таблица вроде этого:SQL Server: разделить запись

account | check1   | check2 
1   | 100]200]300  | 101]209]305 
2   | 401]502   | 404]511 
3   | 600    | 601 

Я хочу, чтобы отделить записи во что-то вроде этого:

account | check1  | check2 
1   | 100  | 101 
1   | 200  | 209 
1   | 300  | 305 
2   | 401  | 404 
2   | 502  | 511 
.   |  .  | . 
.   |  .  | . 
.   |  .  | . 

Как сделать это с помощью SQL сервер только?

Спасибо,

+2

http://whathaveyoutried.com/ –

+1

Начните с реализации функции разделения строк: http://stackoverflow.com/questions/314824 Это, скорее всего, будет в цикле, и вам понадобится второй цикл для разделения результаты функции. Заполните временную таблицу. Вероятно, вы захотите сделать это в хранимой процедуре. –

+0

На стороне примечания, похоже, вы прилагаете усилия к нормализации. Вы также можете рассмотреть возможность создания другого столбца с именем 'CheckNumber', где вместо столбца для каждой проверки вы используете строку для каждой проверки. –

ответ

5

Во-первых, вам нужно раздвоение функцию, которая может позволить вам определить порядок в результате. Это мульти-оператор ТВФ, который использует столбец IDENTITY

CREATE FUNCTION dbo.SplitStrings 
(
    @List  NVARCHAR(MAX), 
    @Delimiter NVARCHAR(255) 
) 
RETURNS @t TABLE(ID INT IDENTITY(1,1), Item INT) 
AS 
BEGIN 
    INSERT @t(Item) SELECT SUBSTRING(@List, Number, 
     CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number) 
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) 
     FROM sys.all_objects) AS n(Number) 
    WHERE Number <= CONVERT(INT, LEN(@List)) 
     AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter 
    ORDER BY Number OPTION (MAXDOP 1); 

    RETURN; 
END 
GO 

(Если у вас есть таблица Numbers, вы можете использовать, что вместо подзапроса, и это также позволит вам добавлять С SCHEMABINDING к определению функции в ., что обеспечивает потенциальные преимущества по производительности)

с функцией в месте, здесь приведен пример использования приведены данные, предоставленные вами и желанной результаты:

DECLARE @x TABLE(account INT, check1 NVARCHAR(1000), check2 NVARCHAR(1000)); 

INSERT @x SELECT 1, '100]200]300','101]209]305' 
UNION ALL SELECT 2, '401]502','404]511' 
UNION ALL SELECT 3, '600','601' 
UNION ALL SELECT 4, '205]104','304]701'; -- I added this sanity check 

SELECT account, check1 = s1.Item, check2 = s2.Item 
FROM @x AS x 
CROSS APPLY dbo.SplitStrings(x.check1, ']') AS s1 
CROSS APPLY dbo.SplitStrings(x.check2, ']') AS s2 
WHERE s1.ID = s2.ID 
ORDER BY account, s1.ID; 

результаты:

account check1 check2 
------- ------ ------ 
1  100  101 
1  200  209 
1  300  305 
2  401  404 
2  502  511 
3  600  601 
4  205  304 
4  104  701 

Это предполагает, что у вас есть какая-либо проверка/принудительное выполнение, что соответствующие значения в столбцах check1 и check2 всегда будут иметь одинаковое количество значений. Он также предполагает, что любое значение check1/check2 не будет превышать около 7000 символов (снова таблица Numbers может помочь сделать ее более гибкой).

EDIT

После комментариев AndriyM, я хотел вернуться и вновь посетить это, в основном, чтобы поставить версию выше функции, которая работает без использования мульти-заявление TVF. Это может использоваться идея Андрея ROW_NUMBER().

CREATE FUNCTION dbo.SplitStrings 
(
    @List  NVARCHAR(MAX), 
    @Delimiter NVARCHAR(255) 
) 
RETURNS TABLE 
AS 
    RETURN (SELECT Number = ROW_NUMBER() OVER (ORDER BY Number), 
     Item FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(@List, Number, 
     CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number))) 
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) 
     FROM sys.all_objects) AS n(Number) 
    WHERE Number <= CONVERT(INT, LEN(@List)) 
     AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter 
    ) AS y); 
GO 
+0

Ваша вещь 'SplitStrings' - это тянуть и вставлять данные за один раз, что означает, что вы можете легко переписать его как встроенный TVF, но вы выбрали многозадачный. Не могли бы вы объяснить, почему? (Я имею в виду, если это было не просто произвольным решением.) Я спрашиваю об этом, потому что с недавних пор я всегда слышал, что встроенные ТВФ обычно должны быть предпочтительнее мульти-заявленных ТВФ (где, естественно, возможны первые здесь, я полагаю). –

+0

@AndriyM вы можете показать встроенный TVF, который не использует столбец IDENTITY, но может гарантировать порядок результатов? Внешний запрос нуждается в некотором роде для соединения от 100 до 101, от 200 до 209 и т. Д. Я думаю, что это можно сделать как встроенный TVF и «работать», но я не думаю, что вы можете гарантировать порядок вывода (вот почему я добавил строка, в которой номера внутри строк не были в порядке возрастания). Я был бы счастлив, если бы ошибался, но это займет больше, чем комментарий. :-) (например, добавьте лучший ответ, если у вас есть ...) –

+0

Я пропустил бит IDENTITY, извините. Я думаю, вы могли бы заменить это внешним выбором с помощью параметра ROW_NUMBER() OVER (ORDER BY Number) AS ID'. Но я не знаю, будет ли это лучше. Во всяком случае, у меня есть ответ на мой вопрос, спасибо. :) –

0

@Aaron Bertrand для записей с нулевым значением во втором столбце 'Check2', как это:

Account | Check1  | Check2 

001  | 100]200  | ] 

002  | 300]400  | Null 

003  | 500]600]700 | ]] 

ваша функция не возвращает значения, как это:

Account | Check1  | Check2 

001  | 100   | 

001  | 200   | 

002  | 300   | Null 

002  | 400   | Null 

003  | 500   | 

003  | 600   | 

003  | 700   | 

Как улучшить вашу функцию для обработки нулевого значения или пустой строки после последнего разделителя?