# پایگاه‌های داده > SQL Server > T-SQL >  حذف کاراکتر های تکراری متوالی از یک رشته

## محمد سلیم آبادی

داشتم یک مقاله از سایت sqlservercentral را راجب sequence numbers table می خواندم که به بخشی با عنوان "حذف کاراکترهای side-by-side تکراری با کمک جدول کمکی اعداد متوالی از یک تا N" رسیدم.

نوسینده از روش جالبی استفاده نکرده بود. در ادامه روش خودم را معرفی می کنم که برای هر سناریویی مورد استفاده می تواند قرار گیرد. و بسیار خوانا و ساده است. (البته با کمک گرفتن از جدول اعداد)

*صورت مساله* 
داده ی زیر را تصور کنید

befor
-------------------------------------------------------------
MMMMoooohhhhaaaaammmaaaaaaaaad             Salimmmmmmabadi

ما نیاز داریم کاراکترهای تکراری کنار هم را حذف کنیم. در نتیجه رشته ی مورد نظر ما این خواهد بود:
 
removed
----------------------
Mohamad Salimabadi



*راه حل جدید:*


--Declaration string variables
DECLARE @s VARCHAR(500) = 'MMMMoooohhhhaaaaammmaaaaaaaaad             Salimmmmmmabadi'
DECLARE @result VARCHAR(500)='';
 
--Publishing auxiliary sequence numbers table with native approach
WITH c AS 
(SELECT 1 AS n  
UNION ALL  
SELECT 1 + n FROM c WHERE n < 100),
c1 AS (SELECT n = ROW_NUMBER() OVER (ORDER BY (SELECT 1))
        FROM c c1
             CROSS JOIN c c2)
 
--Splitting the string with numbs table
, k AS
(SELECT n, k = SUBSTRING (@s, n, 1) 
  FROM c1 
 WHERE n <= LEN(@s))
 
--Filtering characters and then concatenating them
SELECT @result = @result + k
FROM k k1
WHERE NOT EXISTS 
    (SELECT * 
       FROM k k2 
      WHERE k1.k = k2.k 
        AND k1.n+1 = k2.n);
 
 
SELECT @result AS removed;

----------


## fakhravari

با سلام
چطوری میتونم این کد را روی یک جدول پیاده کنم.
مثلا از جدول a  روی رکورد هایی که این شرط را دارن پیاده بشه

----------


## محمد سلیم آبادی

> چطوری میتونم این کد را روی یک جدول پیاده کنم.


شما میتونید یک تابع مخصوص اینکار ایجاد کنید و بعد در کوئری خیلی راحت هر موقع که نیاز داشتین استفاده کنید برای ساخت تابع به لینک زیر مراجعه کنید:
http://www.30sharp.com/article/13/22...%AF%D8%B1.aspx

اگر هم نمیخواهین تابع تعریف کنید. میتونید همه رو در قالب یک کوئری بیارین. 
فرض کنید نام جدولتون your_table هست و کلید جدول Id و ستونی که مقدار رشته ای داره و میخواهین اصلاح بشه هست your_column آنگاه داریم:
with c1 as
(
select *
from your_table t
cross apply (SELECT n , SUBSTRING (your_column, n, 1)
               FROM number_table
              WHERE n <= LEN(your_column)) c(n, k)
),
c2 as
(
select c1.*
from c1 left join c1 as c2
on c1.id = c2.id
and c1.n = c2.n-1
where c1.k <> c2.k or c2.id is null
)
select t.*, replace(d.list,'~@','') as no_replicate from your_table t
cross apply (select '~@'+k
             from c2
             where c2.id = t.id
             order by n
             for xml path('')) d(list);

----------


## fakhravari

ممنون داش *msalim*
والا کمی پیچیده بود.
یه مثال ساده تر در حد پیاده روی این select
SELECT [TopicID],[Message] FROM [B_Topics]

----------


## محمد سلیم آبادی

گل پسر،
خب مثالی که آورده بودم هم دقیقا بر اساس همچین جدولی بود شما کافیه به جای topicid و message و b_topics به ترتیب id و your_column و your_table رو قرار بدین. نکته! از قبل شما باید یک جدول اعداد داشته باشین با کد زیر اونو تولید و مقدار دهی کنید:
create table number_table(n int not null primary key)
insert into number_table(n) select row_number() over(Order by (select 1)) as n from sys.all_columns

یا یه کار دیگه، بعد از ساخت تابع زیر کوئریتون رو به این شکل بنوسید:
select topicid, dbo.fnremovedupesi(message) from b_topics
CREATE FUNCTION dbo.fnRemoveDupesI (@String VARCHAR(8000)) 
 RETURNS VARCHAR(8000) 
 AS
 BEGIN
   DECLARE @result VARCHAR(8000) = '';
    
   SELECT @result = @result + Data
     FROM    (SELECT ID, 
                     Data,
                     ROW_NUMBER() OVER (PARTITION BY Data ORDER BY ID) - ID
                FROM (SELECT SUBSTRING(@String, n, 1), n 
                        FROM Nums 
                       WHERE n <= LEN(@String)
                     ) D(data, ID)
             ) D(ID, Data, RowNum)   
   GROUP BY Data, RowNum
   ORDER BY MIN(ID)
 
   RETURN @result
 END;

----------


## یوسف زالی

سلام.
فکر می کنم به اشتباه خیلی سختش کردید. من این کد رو نوشتم.
بررسی بفرمایید نظرتون رو بگید:

declare @X varchar(100) = 'YYYYOOOOUUUSSSEEEEFFFFFFFFFFF    ZAAALLLLLLIIII'
declare @Z varchar(100) = ''
declare @C char

set @X = REPLACE(@X, ' ', '$');

with CTE as
  (
   select 1 as N
   union all
   select N +1
   from CTE
   where N < LEN(@X)
  )
select @C = SUBSTRING(@X, 1, 1), @Z += @C, @X = REPLACE(LTRIM(REPLACE(@X, @C, ' ')), ' ', @C)
from CTE

set @X = REPLACE(@Z, '$', ' ')
select @X

----------


## محمد سلیم آبادی

سلام،
جالب، الگوریتم زیرکانه ای داره. برای حذف کاراکترهای تکراری متوالی که در اول رشته موجود هستند از ltrim کمک گرفته شده.
کد زیر در واقع عین الگوریتم شماست ولی با یک شیوه پیاده سازی متفاوت، در این روش ما دیگه درگیر کاراکتر های space نمیشیم. و رشته رو به یک طریق دیگه برش میدیم.
declare @X varchar(100) = 'YYYYOOOOUUUSSSEEEEFFFFFFFFFFF    ZAAALLLLLLIIII',
        @Z varchar(100) = '', 
        @C char,
        @P int;
 
select @C = SUBSTRING(@X, 1, 1), 
       @Z += @C,
       @P = patindex('%[^'+@C+']%',@X),
       @X = case when @P > 0 then substring(@X,@p,100) else '' end
from (select top(len(@x))1 n from sys.columns)d
 
set @X = @Z
select @X

سوء تفاهم نشه، هدف حل مساله توسط کوئری هست (یعنی سر و تهش رو در یک کوئری  هم بیاریم) بدون درگیر شدن با بحث انتسابات در select (مثل کد شما)  و یا  تکنیک های دیگه مثل حلقه. 
کوئری هایی که در اولین و دومین پستم مطرح کردم این قابلیت رو دارن که روی مقادیر یکایک سطرهای جدول اعمال بشن یعنی به ازای هر سطری، در cross apply ما مقدار رو به فرمت مناسب در میاریم. ولی در روش شما این امکان وجود نداره که کد داخل یک کوئری بکار بره (نمیشه در یک کوئری هم زمان انتساب و برگرداندن مقادیر داشت)، شما مجبورین که یک تابع تعریف کنید و در بدنه تابع از کدتون استفاده کنید.

----------


## tooraj_azizi_1035

غیر از شهادت راه دیگه ای هم هست. 
سختی کار رو در SQL Server میشه با CLR Integration حل کرد.  یعنی یک User Defined Function در C#‎‎ نوشت و در SQL Server استفاده کرد. 

public static string RemoveDuplicates(string input)
{
    return new string(input.ToCharArray().Distinct().ToArray());
}

----------


## یوسف زالی

جناب tooraj:
اگر بنا به CLR نوشتن بود که ..!
بی خیال!

جناب سلیم،
خدا رو شکر، نگران بودم برای شما سوء تفاهم نشه. خوشحالم که حرفه ای برخورد می کنید.
در مورد توابع حق با شماست. باید بگم که خیلی وقت ها لازم می شه از پیچیدگی کاسته بشه و به خوانایی اضافه بشه.
فکر می کنم تفاوت اصلی میان دو کد مطرح شده بیشتر خوانایی و توانایی اصلاحات و گسترش باشه.
بهتره که این کار در یک تابع باشه.
اما از نظر سرعت ممکنه استفاده مستقیم از کد شما در ردیف های خیلی خیلی زیاد، بهتر باشه. به دلیل ارجاعات  متعدد به تابع. اما در واقع اکثر کارهای آدم با زیر 10,000 ردیف (top)  راه می افته.
اون کد رو با کمی اصلاح هم می شه تبدیل به کدی کرد که مستقیم کار کنه و احتمالا بسیار شبیه نتیجه کار شما بشه.
در کل خوندن و تحلیل اسکریپت های شما رو دوست دارم. نکات "ابتکاری" (تاکید) شما رو دوست دارم.
موفق باشید.

----------


## fakhravari

با سلام
ALTER FUNCTION [dbo].[DelTextDplicate](@Text nvarchar)
RETURNS NVARCHAR
AS
BEGIN

declare @X nvarchar(100) = @Text;
declare @Z nvarchar(100) = '';
declare @C char;
declare @P int;
          
select @C = SUBSTRING(@X, 1, 1),
       @Z += @C,
       @P = patindex('%[^'+@C+']%',@X),
       @X = case when @P > 0 then substring(@X,@p,100) else '' end
from (select top(len(@x))1 n from sys.columns)d
  
set @X = @Z

    RETURN @X
END
و بر روی select
SELECT [TopicID]
      ,[ForumID]
      ,dbo.DelTextDplicate([Subject])as Subject
FROM [B_Topics]
نتیجه در عکس ببنید.
فکر کنم مشک در طول رشته است؟ :متفکر:

----------


## fakhravari

من به NVARCHAR(max) تغیر دادم به شکل زیر شد.
فکر کنم روی رکورد فارسی کاربرد نداره چون جمله بهم میزنه

----------


## محمد سلیم آبادی

> من به NVARCHAR(max) تغیر دادم به شکل زیر شد.
> فکر کنم روی رکورد فارسی کاربرد نداره چون جمله بهم میزنه


 حقیقتش من روی متون فارسی راه حل رو بررسی نکردم. نتیجتا کد برای جملات فارسی ضمانتی نداره.

----------

