2012-03-28 5 views
3

У меня есть фид, который заполняет одно текстовое поле в таблице со статистикой. Мне нужно перетащить эти данные в несколько полей в другую таблицу , но странный формат затрудняет импорт.Анализ текста на несколько столбцов

Формат файла плоский текст, но пример ниже:

08:34:52 Checksum=180957248,TicketType=6,InitialUserType=G,InitialUserID=520,CommunicationType=Incoming,Date=26-03-2012,Time=08:35:00,Service=ST,Duration=00:00:14,Cost=0.12 

Эффективно он состоит из:

[timestamp] [Field1 name]=[Field1 value],[Field2 name]=[Field2 value],[Field4 name]=[Field4 value]...[CR] 

Все поля всегда в том же порядке, но не всегда присутствует , Всего столбцов может быть от 5 до 30.

Я попытался ниже функцию, чтобы перевести его, который, кажется, работает в основном, но, кажется, случайно пропустить поля:

разборе данные:

(SELECT [Data].[dbo].[GetFromTextString] ('Checksum=' ,',' ,RAWTEXT)) AS RowCheckSum, 
(SELECT [Data].[dbo].[GetFromTextString] ('TicketType=' ,',' ,RAWTEXT)) AS TicketType, 

И функция:

CREATE FUNCTION [dbo].[GetFromTextString] 
-- Input start and end and return value. 
    (@uniqueprefix VARCHAR(100), 
    @commonsuffix VARCHAR(100), 
    @datastring VARCHAR(MAX)) 
RETURNS VARCHAR(MAX) -- Picked Value. 
AS 
BEGIN 

    DECLARE @ADJLEN INT = LEN(@uniqueprefix) 

    SET @datastring = @datastring + @commonsuffix 

    RETURN ( 
    CASE WHEN (CHARINDEX(@uniqueprefix,@datastring) > 0) 
     AND (CHARINDEX(@uniqueprefix + @commonsuffix,@datastring) = 0) 
    THEN SUBSTRING(@datastring, PATINDEX('%' + @uniqueprefix + '%',@datastring)[email protected], CHARINDEX(@commonsuffix,@datastring,PATINDEX('%' + @uniqueprefix + '%',@datastring))- PATINDEX('%' + @uniqueprefix + '%',@datastring)[email protected]) ELSE NULL END 
) 
END 

может кто-нибудь предложить лучший/более рациональный способ вычистить данные или кто-то мог понять, почему эта формула лыжи ps строк?

Любая помощь действительно ценится.

+0

Это должно быть сделано в SQL? Было бы гораздо проще сделать это на другом уровне, который поддерживает массивы и пары ключевого значения. – GarethD

+0

Какой бренд SQL вы используете? – MatBailie

+0

Единственными инструментами, которые я имею в наличии, являются SQL Server 2008 и Crystal Reports 2008, я мог бы получить доступ к данным через приложение VB.net, но это нужно было бы запускать на клиентской машине, я бы предпочел автоматизировать процесс (когда на сервере нет нагрузки), так как потенциальные ряды будут 5000+ за ночь. – bendataclear

ответ

3

ПРИМЕЧАНИЕ. ПЕРВОЕ РЕШЕНИЕ РЕЗЬБЫ. Я ушла в ИТ по историческим причинам, НО ЛУЧШЕ РЕШЕНИЕ СОДЕРЖИТСЯ НИЖЕ

Я даже не уверен, если это будет быстрее, чем ваш текущий метод, но это так, как я бы подойти к этому вопросу (если я был принудительно в SQL-решение). Первое, что требуется, это таблица, функция, которая будет выполнять функцию разделения:

CREATE FUNCTION dbo.Split (@TextToSplit VARCHAR(MAX), @Delimiter VARCHAR(MAX)) 
RETURNS @Values TABLE (Position INT IDENTITY(1, 1) NOT NULL, TextValues VARCHAR(MAX) NOT NULL) 
AS 
BEGIN 
    WHILE CHARINDEX(@Delimiter, @TextToSplit) > 0 
     BEGIN 
      INSERT @Values 
      SELECT LEFT(@TextToSplit, CHARINDEX(@Delimiter, @TextToSplit) - 1) 
      SET @TextToSplit = SUBSTRING(@TextToSplit, CHARINDEX(@Delimiter, @TextToSplit) + 1, LEN(@TextToSplit)) 

     END 
     INSERT @Values VALUES (@TextToSplit) 
    RETURN 
END 

В моем примере я работаю из временной таблицы @Worklist, что вам может понадобиться, чтобы адаптировать ваше соответствующим образом, или вы могли бы просто вставить соответствующие данные в @Worklist, где я использовал фиктивные данные:

DECLARE @WorkList TABLE (ID INT IDENTITY(1, 1) NOT NULL, TextField VARCHAR(MAX)) 
INSERT @WorkList 
SELECT '08:34:52 Checksum=180957248,TicketType=6,InitialUserType=G,InitialUserID=520,CommunicationType=Incoming,Date=26-03-2012,Time=08:35:00,Service=ST,Duration=00:00:14,Cost=0.12' 
UNION 
SELECT '08:34:52 Checksum=180957249,TicketType=5,InitialUserType=H,InitialUserID=521,CommunicationType=Outgoing,Date=27-03-2012,Time=14:27:00,Service=ST,Duration=00:15:12,Cost=0.37' 

основной бит запроса делается здесь. Это довольно долго, поэтому я попытался как можно скорее прокомментировать это. Если требуется дополнительное разъяснение, я могу добавить дополнительные комментарии.

DECLARE @Output TABLE (ID INT IDENTITY(1, 1) NOT NULL, TextField VARCHAR(MAX)) 
DECLARE @KeyPairs TABLE (WorkListID INT NOT NULL, KeyField VARCHAR(MAX), ValueField VARCHAR(MAX)) 

-- STORE TIMESTAMP DATA - THIS ASSUMES THE FIRST SPACE IS THE END OF THE TIMESTAMP 
INSERT @KeyPairs 
SELECT ID, 'TimeStamp', LEFT(TextField, CHARINDEX(' ', TextField)) 
FROM @WorkList 

-- CLEAR THE TIMESTAMP FROM THE WORKLIST 
UPDATE @WorkList 
SET  TextField = SUBSTRING(TextField, CHARINDEX(' ', TextField) + 1, LEN(TextField)) 

DECLARE @ID INT = (SELECT MIN(ID) FROM @WorkList) 
WHILE @ID IS NOT NULL 
    BEGIN 
     -- SPLIT THE STRING FIRST INTO ALL THE PAIRS (e.g. Checksum=180957248) 
     INSERT @Output 
     SELECT TextValues 
     FROM dbo.Split((SELECT TextField FROM @WorkList WHERE ID = @ID), ',') 

     DECLARE @ID2 INT = (SELECT MIN(ID) FROM @Output) 

     -- FOR ALL THE PAIRS SPLIT THEM INTO A KEY AND A VALUE (USING THE POSITION OF THE SPLIT FUNCTION) 
     WHILE @ID2 IS NOT NULL 
      BEGIN 
       INSERT @KeyPairs 
       SELECT @ID, 
         MAX(CASE WHEN Position = 1 THEN TextValues ELSE '' END), 
         MAX(CASE WHEN Position = 2 THEN TextValues ELSE '' END) 
       FROM dbo.Split((SELECT TextField FROM @Output WHERE ID = @ID2), '=') 

       DELETE @Output 
       WHERE ID = @ID2 

       SET @ID2 = (SELECT MIN(ID) FROM @Output) 
      END 

     DELETE @WorkList 
     WHERE ID = @ID 

     SET @ID = (SELECT MIN(ID) FROM @WorkList) 
    END 

-- WE NOW HAVE A TABLE CONTAINING EAV MODEL STYLE DATA. THIS NEEDS TO BE PIVOTED INTO THE CORRECT FORMAT 
-- ENSURE COLUMNS ARE LISTED IN THE ORDER YOU WANT THEM TO APPEAR 
SELECT * 
FROM @KeyPairs p 
     PIVOT 
     ( MAX(ValueField) 
      FOR KeyField IN 
       ( [TimeStamp], [Checksum], [TicketType], [InitialUserType], 
        [InitialUserID], [CommunicationType], [Date], [Time], 
        [Service], [Duration], [Cost] 
       ) 
     ) AS PivotTable; 

EDIT (4 ЛЕТ СПУСТЯ)

Недавно upvote обратил на это мое внимание, и я ненавижу себя немного навсегда разместить этот ответ в его нынешнем виде.

Гораздо лучше функция разделения будет:

CREATE FUNCTION dbo.Split 
(
    @List  NVARCHAR(MAX), 
    @Delimiter NVARCHAR(255) 
) 
RETURNS TABLE 
WITH SCHEMABINDING AS 
RETURN 
( WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1), (1)) n (N)), 
    N2(N) AS (SELECT 1 FROM N1 a CROSS JOIN N1 b), 
    N3(N) AS (SELECT 1 FROM N2 a CROSS JOIN N2 b), 
    N4(N) AS (SELECT 1 FROM N3 a CROSS JOIN N3 b), 
    cteTally(N) AS 
    ( SELECT 0 UNION ALL 
     SELECT TOP (DATALENGTH(ISNULL(@List,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) 
     FROM n4 
    ), 
    cteStart(N1) AS 
    ( SELECT t.N+1 
     FROM cteTally t 
     WHERE (SUBSTRING(@List,t.N,1) = @Delimiter OR t.N = 0) 
    ) 
    SELECT Item = SUBSTRING(@List, s.N1, ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000)), 
      Position = s.N1, 
      ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1) 
    FROM cteStart s 
); 

Тогда нет необходимости зацикливания на всех, вы просто правильное решение, основанное множества путем вызова функции разделения дважды, чтобы получить набор данных EAV стиля :

DECLARE @WorkList TABLE (ID INT IDENTITY(1, 1) NOT NULL, TextField VARCHAR(MAX)) 
INSERT @WorkList 
SELECT '08:34:52 Checksum=180957248,TicketType=6,InitialUserType=G,InitialUserID=520,CommunicationType=Incoming,Date=26-03-2012,Time=08:35:00,Service=ST,Duration=00:00:14,Cost=0.12' 
UNION 
SELECT '08:34:52 Checksum=180957249,TicketType=5,InitialUserType=H,InitialUserID=521,CommunicationType=Outgoing,Date=27-03-2012,Time=14:27:00,Service=ST,Duration=00:15:12,Cost=0.37'; 

WITH KeyPairs AS 
( SELECT w.ID, 
      [Timestamp] = LEFT(w.TextField, CHARINDEX(' ', w.TextField)), 
      KeyField = MAX(CASE WHEN v.ItemNumber = 1 THEN v.Item END), 
      ValueField = MAX(CASE WHEN v.ItemNumber = 2 THEN v.Item END) 
    FROM @WorkList AS w 
      CROSS APPLY dbo.Split(SUBSTRING(TextField, CHARINDEX(' ', TextField) + 1, LEN(TextField)), ',') AS kp 
      CROSS APPLY dbo.Split(kp.Item, '=') AS v 
    GROUP BY w.ID, kp.ItemNumber,w.TextField 
) 
SELECT * 
FROM KeyPairs AS kp 
     PIVOT 
     ( MAX(ValueField) 
      FOR KeyField IN 
       ( [Checksum], [TicketType], [InitialUserType], 
        [InitialUserID], [CommunicationType], [Date], [Time], 
        [Service], [Duration], [Cost] 
       ) 
     ) AS pvt; 
+0

Это похоже на отличное решение, проверенное на некоторых данных, и оно быстро запускается с помощью select. Если бы я хотел использовать это для вставки, как бы он обрабатывал нулевые/отсутствующие значения или если ключ отсутствует во всех строках? Мне нужно настроить мою постоянную таблицу, чтобы включить все 30 полей, но у них нет возможности. – bendataclear

+1

Он просто вернет 'NULL', если для определенного ключа нет значения. – GarethD

+0

Но если я выполняю 'INSERT INTO MyTable SELECT * FROM @Keypairs ...', он будет вставлять правильные значения в правильные столбцы или будет ли он вставлять нулевые значения для отсутствующих столбцов? – bendataclear

Смежные вопросы