2011-04-26 57 views
8

我有我想從上面的字符串我要刪除它的多個逗號',,,sam,,bob,'',,,' 存儲過程的字符串,它必須看起來像 'sam,bob,'或者只',,,'然後''。 我只能使用Sql Server函數。 即時通訊使用Sql Server 2008和.Net 3.5去除重複複製的字符

在此先感謝。

+0

在插入/傳遞數據之前,您是否有機會做到這一點?如果是這樣,使用代碼可能會更容易。 – 2011-04-26 17:40:24

+0

其漂亮的舊存儲過程。包含超過4k行代碼。所以不能插入/傳遞。我打開編寫UDF在SQL Server不通過代碼 – Nash 2011-04-26 17:48:47

回答

7

這適用於那些專門逗號或有多達398個連續的逗號字符串。

SELECT 
    CASE 
     WHEN TargetString NOT LIKE '%[^,]%' 
      THEN '' /*The string is exclusively commas*/ 
     ELSE 
      REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(TargetString, 
      REPLICATE(',',16),','), /*399/16 = 24 remainder 15*/ 
      REPLICATE(',',8),','), /* 39/ 8 = 4 remainder 7*/ 
      REPLICATE(',',4),','), /* 11/ 4 = 2 remainder 3*/ 
      REPLICATE(',',2),','), /* 5/ 2 = 2 remainder 1*/ 
      REPLICATE(',',2),',') /* 3/ 2 = 1 remainder 1*/ 
     END 
FROM T  

在頂部添加的2個額外的權力,如果你需要更多的,或者如果你需要更少的從頂部取出。每個階段的評論表明這個階段不會成功處理的最小數量。

所有註釋行都是這種格式

/* L/D = Q remainder R */ 

D: Corresponds to the length of the string generated by `REPLICATE` 
R: Is always D-1 
Q+R: Form L for the next step 

所以與其他REPLICATE(',',32),',')階段

D = 32 
R = 31 
Q = 368 (399-31) 
L = (368 * 32) + 31 = 11807 

向上延伸串聯,以便將處理多達11,806個字符逗號的部分。

5

我會建議一個UDF來做到這一點。由於我即將提出的UDF不會觸及任何表格,因此表現應該相當不錯。

CREATE Function [dbo].[CleanDuplicates](@Data VarChar(8000), @DuplicateChar VarChar(1)) 
Returns VarChar(8000) 
WITH SCHEMABINDING 
AS 
Begin 

    Set @Data = @DuplicateChar + @Data 

    While PATINDEX('%' + @DuplicateChar + @DuplicateChar + '%',@Data) > 0 
     Set @Data = REPLACE(@Data, @DuplicateChar + @DuplicateChar,@DuplicateChar) 

    Return Right(@Data, Len(@Data)-1) 

End 

您可以測試的功能是這樣的:

Select dbo.CleanDuplicates(',,,', ',') 
Select dbo.CleanDuplicates(',,,sam,,bob,', ',') 
+0

時使用標量UDF,性能將不是「很好」,因爲它將重新計算每行 – Dalex 2011-04-27 12:11:27

+0

@Dalex,感謝您的評論。不幸的是,你過度簡化了標量UDF性能問題。我想你會驚訝地發現你的查詢比我的慢很多倍。當UDF使用表時,標量UDF往往表現不佳。由於這個UDF沒有,所以性能很好。我也喜歡它簡單易懂,代碼快速且易於調試。 – 2011-04-27 12:40:48

+0

最好的做法是對不做數據訪問的UDF使用'WITH SCHEMABINDING'選項。不確定這是否會以任何方式直接獲得「SELECT」,但如果UDF用於UPDATE語句中(如此處所述),則肯定有益(http://blogs.msdn.com/b/sqlprogrammability/archive/) 2006/05/12/596424.aspx) – 2011-04-27 13:53:15

0

您的解決方案是好的,但

  1. 是逗號只
  2. 我恨基於循環的TSQL代碼;-)

所以我寫了基於基於集合的馬辛解決方案的通用代碼用於替換每種已申報種類的副本:

DECLARE @Duplicate NVARCHAR(100)= '#$' 
DECLARE @TestString NVARCHAR(MAX)= 'test_test__f##f2$$g' 
DECLARE @Replacement NVARCHAR(MAX)= '' 
DECLARE @OutputString NVARCHAR(MAX)= @teststring ; 
WITH numbers 
      AS (SELECT ROW_NUMBER() OVER (ORDER BY o.object_id, o2.object_id) Number 
       FROM  sys.objects o 
         CROSS JOIN sys.objects o2 
      ), 
     chars 
      AS (SELECT SUBSTRING(@Duplicate, 1, 1) CHAR , 
         CAST(1 AS INT) [LEVEL] 
       UNION ALL 
       SELECT SUBSTRING(@Duplicate, numbers.Number, 1) CHAR , 
         CAST(numbers.Number AS INT) [LEVEL] 
       FROM  numbers 
         JOIN chars ON chars.Level + 1 = numbers.Number 
       WHERE LEN(SUBSTRING(@Duplicate, numbers.Number, 1)) > 0 
      ), 
     Replicated 
      AS (SELECT REPLICATE(CHAR, numbers.number) Repl , 
         numbers.Number 
       FROM  chars 
         CROSS JOIN numbers 
      ) 
    SELECT @OutputString = REPLACE(@OutputString, Repl, @Replacement) 
    FROM replicated 
    WHERE number <= LEN(@TestString) 

SELECT @OutputString 

您可以聲明每一種o重複字符串中的f char和@Replacement中的每個替換字符串。 附加增益恕我直言,是,我只在輸入字符串的最大長度的範圍內尋找替代

+0

>> 2.i討厭基於循環的TSQL代碼;-)如果這是真的,你可能想擺脫對計數器使用rCTE(遞歸CTE)的習慣。與其他方法相比,rCTE對某些事物有好處,但對計數來說非常可怕,因爲在幕後,它們在臨時表上是一個可怕的循環。這裏是關於這個主題的文章的鏈接。 http://www.sqlservercentral.com/articles/T-SQL/74118/ – 2012-07-07 04:04:54

2

試試這個

SELECT @Parameter AS 'BEFORE' 
BEGIN 
WHILE CHARINDEX(',,', @Parameter) > 0 
    BEGIN 
     SELECT @Parameter = REPLACE(@Parameter, ',,',',') 
    END 
SELECT @Parameter AS 'AFTER' 
END 
1

喬治Mastros寫道:


我會建議一個UDF來做到這一點。由於UDF我將建議 不接觸任何表格,所以表現應該相當不錯。

我同意「僅限內存」Scalar UDF的速度非常快。事實上,我實際上使用了George的Scalar UDF中的一個,它解決了「初始上限」問題,以證明有時「基於集合」的代碼始終是最佳途徑。

但是,馬丁史密斯(另一個海報在這個線程)絕對是正確的軌道上。在這種情況下,「基於集合」仍然是一條路。當然,任何人都可以對性能做出毫無事實根據的聲明,所以讓我們通過性能演示加熱它。

爲了演示,我們首先需要一些測試數據。大量的測試數據,因爲我們要測試的兩個函數運行得很快。這是構建一百萬行測試表的代碼。

--===== Conditionally drop the test table 
    -- to make reruns in SSMS easier 
    IF OBJECT_ID('tempdb..#MyHead','U') IS NOT NULL 
     DROP TABLE #MyHead 
GO 
--===== Create and populate the test table on-the-fly. 
    -- This builds a bunch of GUIDs and removes the dashes from them to 
    -- increase the chances of duplicating adjacent characters. 
    -- Not to worry. This takes less than 7 seconds to run because of 
    -- the "Pseudo Cursor" created by the CROSS JOIN. 
SELECT TOP 1000000 
     RowNum  = IDENTITY(INT,1,1), 
     SomeString = REPLACE(CAST(NEWID() AS VARCHAR(36)),'-','') 
    INTO #MyHead 
    FROM sys.all_columns ac1 
    CROSS JOIN sys.all_columns ac2 
; 
GO 

沒有必要重新發布喬治的罰款功能,但我確實需要發佈我的。以下函數產生與George相同的結果。它看起來像一個「iTVF」(內嵌表值函數),它只是返回一個值。這就是爲什麼微軟稱他們爲「內聯標量函數」(我簡稱爲「iSFs」)。

CREATE FUNCTION dbo.CleanDuplicatesJBM 
     (@Data VARCHAR(8000), @DuplicateChar VARCHAR(1)) 
RETURNS TABLE WITH SCHEMABINDING AS 
RETURN 
SELECT Item = STUFF(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
        @[email protected] COLLATE LATIN1_GENERAL_BIN, 
       REPLICATE(@DuplicateChar,33),@DuplicateChar), 
       REPLICATE(@DuplicateChar,17),@DuplicateChar), 
       REPLICATE(@DuplicateChar, 9),@DuplicateChar), 
       REPLICATE(@DuplicateChar, 5),@DuplicateChar), 
       REPLICATE(@DuplicateChar, 3),@DuplicateChar), 
       REPLICATE(@DuplicateChar, 2),@DuplicateChar), 
       REPLICATE(@DuplicateChar, 2),@DuplicateChar) 
       ,1,1,'') 
; 
GO 

首先,我們來測試George的Scalar UDF。請閱讀關於爲什麼我們不在這裏使用SET STATISTICS TIME ON的評論。

/****************************************************************************** 
Test George's code. 
Since Scalar Functions don't work well with SET STATISTICS TIME ON, we measure 
duration a different way. We'll also throw away the result in a "Bit Bucket" 
variable because we're trying to measure the performance of the function 
rather than how long it takes to display or store results. 
******************************************************************************/ 
--===== Declare some obviously named variables 
DECLARE @StartTime DATETIME, 
     @BitBucket VARCHAR(36) 
; 
--===== Start the "Timer" 
SELECT @StartTime = GETDATE() 
; 
--===== Run the test on the function 
SELECT @BitBucket = [dbo].[CleanDuplicates](SomeString,'A') 
    FROM #MyHead 
; 
--===== Display the duration in milliseconds 
    PRINT DATEDIFF(ms,@StartTime,GETDATE()) 
; 
--===== Run the test a total of 5 times 
GO 5 

這裏是從 「五元美鈔」 運行的回報...

Beginning execution loop 
15750 
15516 
15543 
15480 
15510 
Batch execution completed 5 times. 
(Average is 15,559 on my 10 year old, single 1.8Ghz CPU) 

現在,我們將運行 「ISF」 版本...

/****************************************************************************** 
Test Jeff's code. 
Even though this uses an "iSF" (Inline Scalar Function), we'll test exactly 
the same way that we tested George's code so we're comparing apples-to-apples. 
This includes throwing away the result in a "Bit Bucket" variable because 
we're trying to measure the performance of the function rather than how long 
it takes to display or store results. 
******************************************************************************/ 
--===== Declare some obviously named variables 
DECLARE @StartTime DATETIME, 
     @BitBucket VARCHAR(36) 
; 
--===== Start the "Timer" 
SELECT @StartTime = GETDATE() 
; 
--===== Run the test on the function 
SELECT @BitBucket = cleaned.ITEM 
    FROM #MyHead 
    CROSS APPLY [dbo].[CleanDuplicatesJBM](SomeString,'A') cleaned 
; 
--===== Display the duration in milliseconds 
    PRINT DATEDIFF(ms,@StartTime,GETDATE()) 
; 
--===== Run the test a total of 5 times 
GO 5 

這裏有該運行的結果。

Beginning execution loop 
6856 
6810 
7020 
7350 
6996 
Batch execution completed 5 times. 
(Average is 7,006 {more than twice as fast} on my 10 year old, single 1.8Ghz CPU) 

我的觀點不是喬治的代碼是壞的。一點也不。事實上,當沒有「單一查詢」解決方案時,我使用Scalar UDF。我還會指出並不是所有的「單一查詢」解決方案都是最好的,並且支持喬治。

就UDF而言,不要停止尋找它們。 ;-)

+0

嘿......是的。我意識到我回應了一個超過一年的線程。我希望有人可以在這段時間之後使用這個技巧。 – 2012-07-07 04:14:38