2012-03-28 4 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] 

.

(SELECT [Data].[dbo].[GetFromTextString] ('Checksum=' ,',' ,RAWTEXT)) AS RowCheckSum, 
(SELECT [Data].[dbo].[GetFromTextString] ('TicketType=' ,',' ,RAWTEXT)) AS TicketType, 
: 데이터를 구문 분석

: 총 열 다섯에서 내가 주로 작동하는 것 같다하지만 무작위로 필드를 건너 뛸 것으로 보인다 번역 할 수있는 아래의 기능을 시도했습니다 (30)

에 어느 곳이 될 수

그리고 기능 :

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

주 - 첫 번째 해결책은 농담입니다. 나는 역사적인 이유로 IT에 남아 있지만 더 좋은 해결책은 아래에 포함되어 있습니다.

이것이 현재의 방법보다 빠르지는 모르겠지만 문제가 될 수 있습니다. SQL 전용 솔루션으로 강제 적용). 내 예를 들어 내가 임시 테이블 @Worklist에서하고 있어요

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에 삽입하십시오.

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; 

편집

(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

이는 훌륭한 솔루션 인 것처럼 보이며 일부 데이터에서 테스트되고 선택을 사용하여 빠르게 실행됩니다. 이것을 삽입에 사용하려면 null/missing values를 처리하거나 모든 행에 키가없는 경우 어떻게해야합니까? 30 개의 모든 필드를 포함하도록 영구 테이블을 설정해야하지만 null이 가능하도록 설정해야합니다. – bendataclear

+1

특정 키에 값이 없으면 그냥 'NULL'을 반환합니다. – GarethD

+0

하지만 'INSERT INTO MyTable SELECT * FROM @Keypairs ...'명령을 실행하면 올바른 값을 올바른 열에 삽입 할 수 있습니까? 그렇지 않으면 누락 된 열에 null 값이 삽입됩니까? – bendataclear