2011-04-26 81 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