2014-01-27 53 views
45
SELECT REPLACE('<strong>100</strong><b>.00 GB', '%^(^-?\d*\.{0,1}\d+$)%', ''); 

我想用上面的正則表達式替換數字的兩個部分之間的任何標記,但它似乎不工作。我不確定這是不是正則表達式語法,因爲我嘗試了一個簡單的例如'%[^0-9]%'只是爲了測試,但它也沒有工作。有誰知道我怎麼能做到這一點?SQL中的正則表達式模式替換函數?

+3

你可能想重溫了答案。 – Mukus

+1

你想要最終的結果是什麼?你期望'100.00'還是'100.00 GB'?是否還有格式化數字的其他示例不符合標記模式,只能在小數點左邊的部分左右?標記可以圍繞整個數字,例如'100 .00 GB'?在右邊總是有兩個字符的貨幣代碼? –

+0

@srutzky我想要小數點的數字,如果有的話,並不是所有的值都有它們,也沒有這些模式,因爲它是第三方的html生成器。有時貨幣有時在數字後面,有時候是符號 - 美元,有時代碼 - 美元,沒有空格..等等。只是非常垃圾的數據 – johnyTee

回答

45

您可以使用PATINDEX 查找模式(字符串)出現的第一個索引。然後使用STUFF將另一個字符串填充到匹配的模式(字符串)中。

循環遍歷每一行。用你想要的替換每個非法字符。在你的情況下,用空白替換非數字。內循環是如果在循環的當前單元中有多個非法字符。

DECLARE @counter int 

SET @counter = 0 

WHILE(@counter < (SELECT MAX(ID_COLUMN) FROM Table)) 
BEGIN 

    WHILE 1 = 1 
    BEGIN 
     DECLARE @RetVal varchar(50) 

     SET @RetVal = (SELECT Column = STUFF(Column, PATINDEX('%[^0-9.]%', Column),1, '') 
     FROM Table 
     WHERE ID_COLUMN = @counter) 

     IF(@RetVal IS NOT NULL)  
      UPDATE Table SET 
      Column = @RetVal 
      WHERE ID_COLUMN = @counter 
     ELSE 
      break 
    END 

    SET @counter = @counter + 1 
END 

注意:這雖然很慢!有一個varchar列可能會影響。所以使用LTRIM RTRIM可能會有所幫助。無論如何,這是緩慢的。

積分爲this StackOverFlow答案。

編輯 信用此亦@srutzky

編輯(由@Tmdean) 不是一次做一排,這個答案可以適應更基於集合的解決方案。它仍然在單行中迭代非數字字符的數量的最大值,所以它並不理想,但我認爲在大多數情況下它應該是可以接受的。

WHILE 1 = 1 BEGIN 
    WITH q AS 
     (SELECT ID_Column, PATINDEX('%[^0-9.]%', Column) AS n 
     FROM Table) 
    UPDATE Table 
    SET Column = STUFF(Column, q.n, 1, '') 
    FROM q 
    WHERE Table.ID_Column = q.ID_Column AND q.n != 0; 

    IF @@ROWCOUNT = 0 BREAK; 
END; 

如果您在表格中保留指示字段是否已被清理的位列,您也可以提高效率。 (NULL代表我的例子「未知」,並應列缺省值。)

DECLARE @done bit = 0; 
WHILE @done = 0 BEGIN 
    WITH q AS 
     (SELECT ID_Column, PATINDEX('%[^0-9.]%', Column) AS n 
     FROM Table 
     WHERE COALESCE(Scrubbed_Column, 0) = 0) 
    UPDATE Table 
    SET Column = STUFF(Column, q.n, 1, ''), 
     Scrubbed_Column = 0 
    FROM q 
    WHERE Table.ID_Column = q.ID_Column AND q.n != 0; 

    IF @@ROWCOUNT = 0 SET @done = 1; 

    -- if Scrubbed_Column is still NULL, then the PATINDEX 
    -- must have given 0 
    UPDATE table 
    SET Scrubbed_Column = CASE 
     WHEN Scrubbed_Column IS NULL THEN 1 
     ELSE NULLIF(Scrubbed_Column, 0) 
    END; 
END; 

如果你不想改變你的模式,這是很容易適應存儲在一個表值變量中間結果其中最後應用於實際表格。

+0

我會嘗試,當我有時間,謝謝! – johnyTee

+2

爲了使此解決方案有效,至少需要爲PATINDEX模式添加句點;它應該是:'[^ 0-9。]'。如果不是,那麼你去掉小數點,把'100.00'變成'10000'。 –

+0

@srutzky ok添加'。'我實際上是在研究非字母表,並認爲做^ 0-9會工作。 – Mukus

18

從一般意義上說,SQL Server不支持正則表達式,並且不能在本機T-SQL代碼中使用它們。

你可以寫一個CLR函數來做到這一點。例如,請參閱here

+1

好吧,那似乎是唯一的方法去...然後感謝 – johnyTee

12

而不是剝離找到的字符的唯一位置,使用Replace(Column, BadFoundCharacter, '')可能會大大加快。此外,不是隻替換每個列中下一個找到的錯誤字符,而是替換所有找到的字符。

WHILE 1 = 1 BEGIN 
    UPDATE dbo.YourTable 
    SET Column = Replace(Column, Substring(Column, PatIndex('%[^0-9.-]%', Column), 1), '') 
    WHERE Column LIKE '%[^0-9.-]%' 
    If @@RowCount = 0 BREAK; 
END; 

我相信這會比接受的答案更好,如果僅僅因爲它的操作更少。還有其他方法可能會更快,但我現在沒有時間去探索這些方法。

+0

看起來很有趣,我現在沒有時間去嘗試,但是當我有時,我會去做。歡呼 – johnyTee

+2

這幫助我一個有點不相關的問題。我使用了替換(Column,Substring(Column,PatIndex('%[^ 0-9 .-]%',Column),1),'')'位。那謝謝啦! – jyoseph

+1

@jyoseph太棒了!請注意,這隻會刪除特定不良字符的所有實例,並且如果一組壞字符大於一個,則必須重複運行... – ErikE

2

如果您想重複使用,將解決方案包裝在SQL函數中可能會很有用。 我甚至做它在細胞水平上,那爲什麼我把這個在一個不同的答案:

CREATE FUNCTION [dbo].[fnReplaceInvalidChars] (@string VARCHAR(300)) 
RETURNS VARCHAR(300) 
BEGIN 
    DECLARE @str VARCHAR(300) = @string; 
    DECLARE @Pattern VARCHAR (20) = '%[^a-zA-Z0-9]%'; 
    DECLARE @Len INT; 
    SELECT @Len = LEN(@String); 
    WHILE @Len > 0 
    BEGIN 
     SET @Len = @Len - 1; 
     IF (PATINDEX(@Pattern,@str) > 0) 
      BEGIN 
       SELECT @str = STUFF(@str, PATINDEX(@Pattern,@str),1,'');  
      END 
     ELSE 
     BEGIN 
      BREAK; 
     END 
    END  
    RETURN @str 
END 
1

如果你這樣做只是爲了進入存儲過程的參數,你可以使用以下命令:

while PatIndex('%[^0-9]%', @Param) > 0 
    select @Param = Replace(@Param, Substring(@Param, PatIndex('%[^0-9]%', @Param), 1), '') 
0

這裏是我寫的基於以前的答案完成這個遞歸函數。

CREATE FUNCTION dbo.RecursiveReplace 
(
    @P_String VARCHAR(MAX), 
    @P_Pattern VARCHAR(MAX), 
    @P_ReplaceString VARCHAR(MAX), 
    @P_ReplaceLength INT = 1 
) 
RETURNS VARCHAR(MAX) 
BEGIN 
    DECLARE @Index INT; 

    -- Get starting point of pattern 
    SET @Index = PATINDEX(@P_Pattern, @P_String); 

    IF @Index > 0 
    BEGIN 
     -- Perform the replace 
     SET @P_String = STUFF(@P_String, PATINDEX(@P_Pattern, @P_String), @P_ReplaceLength, @P_ReplaceString); 

     -- Recurse 
     SET @P_String = dbo.RecursiveReplace(@P_String, @P_Pattern, @P_ReplaceString, @P_ReplaceLength); 
    END; 

    RETURN @P_String; 
END; 

Gist

1

我碰到這個職位跌跌撞撞尋找別的東西,但想我會提到一個解決方案,我使用的是更爲高效 - 與使用時確實應該是任何函數的默認實現一個基於集合的查詢 - 這是使用交叉應用表函數。似乎該主題仍然活躍,所以希望這對某人有用。

基於運行基於遞歸集合的查詢或標量函數,基於1m行測試集從一個隨機newid中移除字符,到WHILE循環示例的範圍從34s到2m05s,從1m3s到{forever}用於函數示例。

在交叉應用中使用表函數在10s中實現了相同的目標。您可能需要調整它以適應您的需求,例如它處理的最大長度。

功能:

CREATE FUNCTION [dbo].[RemoveChars](@InputUnit VARCHAR(40)) 
RETURNS TABLE 
AS 
RETURN 
    (
     WITH Numbers_prep(Number) AS 
      (
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
      ) 
     ,Numbers(Number) AS 
      (
       SELECT TOP (ISNULL(LEN(@InputUnit),0)) 
        row_number() OVER (ORDER BY (SELECT NULL)) 
       FROM Numbers_prep a 
        CROSS JOIN Numbers_prep b 
      ) 
     SELECT 
      OutputUnit 
     FROM 
      (
       SELECT 
        substring(@InputUnit,Number,1) 
       FROM Numbers 
       WHERE substring(@InputUnit,Number,1) like '%[0-9]%' 
       ORDER BY Number 
       FOR XML PATH('') 
      ) Sub(OutputUnit) 
    ) 

用法:

UPDATE t 
SET column = o.OutputUnit 
FROM ##t t 
CROSS APPLY [dbo].[RemoveChars](t.column) o