136

I need to ensure that a given field does not have more than one space (I am not concerned about all white space, just space) between characters.

So

'single    spaces   only'

needs to be turned into

'single spaces only'

The below will not work

select replace('single    spaces   only','  ',' ')

as it would result in

'single  spaces  only'

I would really prefer to stick with native T-SQL rather than a CLR based solution.

Thoughts?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Christoph
  • 4,251
  • 3
  • 24
  • 38
  • 1
    possible duplicate of [SQL method to replace repeating blanks with single blanks](http://stackoverflow.com/questions/2182877/sql-method-to-replace-repeating-blanks-with-single-blanks) – bummi Nov 30 '14 at 22:51

15 Answers15

424

Even tidier:

select string = replace(replace(replace(' select   single       spaces',' ','<>'),'><',''),'<>',' ')

Output:

select single spaces

Neil Knight
  • 47,437
  • 25
  • 129
  • 188
  • 10
    If you want to remove the spaces at the front and end of the string, then wrap the replace in an LTRIM, RTRIM and it will do it for you. – Neil Knight Mar 16 '10 at 15:54
  • 10
    As long as your string doesn't contain a lot of < or > signs. Seems to fragile for my liking. – JohnFx Mar 16 '10 at 15:54
  • 14
    Truly elegant hack. Upvoted. Any two characters could be used for the middle portion if <> are potentially in the input text. – richardtallent Mar 16 '10 at 16:03
  • 1
    That's pretty clever. I'd feel better with a less common character, but this is a cool idea. – BradC Mar 16 '10 at 16:03
  • Like it, but don't think we can safely reserve any characters. We don't have control over the input. – Christoph Mar 16 '10 at 16:33
  • 43
    Chris, you can use non-printable ASCII characters like as CHAR(17) and CHAR(18), as these will NEVER be in your input text. Still faster than the looping of the accepted answer. – richardtallent Mar 16 '10 at 22:23
  • Awesome! I used this in a function to prep a string for splitting and it works great. RTRIM(LTRIM(REPLACE(REPLACE(REPLACE(@StringToSplit, @Delimiter, '<>'), '><', ''), '<>', @Delimiter))) – Soenhay May 15 '14 at 18:20
  • 1
    Whta does >< means and how is it different from <> – sam Jan 03 '15 at 07:56
  • 12
    I had to really look at this for a moment to figure out you you used the '><','' No space replace but now that I get it... it is very brilliant. I did very much like @richardtallent suggestion of using the non-printable ASCII characters the added combination of which produces: REPLACE(REPLACE(REPLACE(LastName,' ','CHAR(17)CHAR(18)'),'CHAR(18)CHAR(17)',''),'CHAR(17)CHAR(18)',' ') – Anthony Griggs Jun 17 '15 at 16:26
  • This technique won't work for type 'TEXT'. "Argument data type text is invalid for argument 1 of replace function." – Paul Totzke Sep 18 '15 at 22:13
  • The idea is great but it may also mean not equal to <>. So if replacing spaces in a query it may truncate the not equal to signs – Sanghita May 05 '16 at 07:02
  • 1
    @sam the reason he used `><` and `<>` is really quite brilliant. What you will end up with is a string that looks something like `<><><><><><>` inbetween words that may be longer or shorter depending on how many spaces there are for the first replace. The second replace takes each string and compacts them to `<>`. Finally the third replace turns each `<>` into a space. Obviously the problem here is that any string that has XML in it won't work with this but as some others have pointed out you can use ASCII characters that aren't printable in it's place – James H May 06 '16 at 15:51
  • this is a brilliant answer. as mentioned in another comment though, it would still be worthwhile to check for angle brackets before running this though just to be safe. also note that, leading / trailing spaces will not be removed from the output (the output in the post doesn't have the leading space from the input); these can easily be trimmed off, however. – John Smith Feb 07 '17 at 17:55
  • 1
    @user3486773 - I'm afraid you're mistaken (and obviously didn't test the answer before you commented). The solution does **exactly** what the OP asked, and works for **any** number of groups of **any** number of spaces. ...I tested it on a 1GB string full of all kinds of junk (and it worked quickly & perfectly.) If you'd like to understand how it works, try breaking it up into the individual functions so you can visualize what each step does. – ashleedawg May 04 '18 at 09:19
  • 4
    @AnthonyGriggs I up'd [your comment](//stackoverflow.com/q/2455750#comment49833480_2455869) because I have `<>` that I need to maintain, so I used your example and it worked. **But then I realized** (while breaking it down like I suggested to the nonbeliever in the previous comment) that your SQL isn't doing what you think: `…'CHAR(17)CHAR(18)'…` replaces with a ***text string***, rather than the ASCII characters — which, funnily enough, *works* — but for the same reason `QQQQQQ` & `ZZZZZZ` would also work: they're also unlikely to be in the string. I'll add to the answer with more info. – ashleedawg May 04 '18 at 10:11
  • Can anyone elaborate on using it in sql server 2016 with using a column name than pre-defining the string? – sc1324 Oct 30 '18 at 15:01
  • @sc1324 change the 'select single spaces' part of the innermost REPLACE with your column name eg `replace(replace(replace(YOUR_COLUMN_NAME,' ','<>'),'><',''),'<>',' ')` – Caius Jard Oct 14 '21 at 05:35
  • @NeilKnight I think it was a useful, if wordy, edit - I'd have perhaps pared it down rather than rolling it back – Caius Jard Oct 14 '21 at 05:39
  • 4
    @AnthonyGriggs, I guess you mean 't using the following expression: `REPLACE(REPLACE(REPLACE(YOUR_COLUMN_NAME, ' ', CHAR(17)+CHAR(18)), CHAR(18)+CHAR(17), ''), CHAR(17)+CHAR(18), ' ')` – SujaiSparks Aug 04 '22 at 12:22
  • You could use any old random jumble of characters as the intermediate string. For example: `REPLACE(REPLACE(REPLACE(Script, ' ', 'q23ji90675'), '90675q23ji', ''), 'q23ji90675', ' ')`. – Stewart Apr 18 '23 at 08:58
  • Alternatively, you could explicitly protect against the characters in your intermediate strings clashing with those already in the string by doing something like this: `REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(TheText, '#', '#h#'), ' ', '#()#'), ')##(', ''), '#()#', ' '), '#h#', '#')` – Stewart Apr 18 '23 at 13:18
34

This would work:

declare @test varchar(100)
set @test = 'this   is  a    test'

while charindex('  ',@test  ) > 0
begin
   set @test = replace(@test, '  ', ' ')
end

select @test
James Wiseman
  • 29,946
  • 17
  • 95
  • 158
  • 1
    Wrapping in function and changing varchar(100) to nvarchar(max) – Christoph Mar 16 '10 at 16:38
  • Difference between James's and Neil's scripts is that James runs though while loop that, in personal experience, having it run though 50,000 records of a table tends to be very slow, so you need to create it as a procedure and pass in a record and some jobs you may not have permission to create a band new procedure. Neil's uses per-existing functions because it uses <>, if you have a string like `"release < now"` then you'll get `"release<><><<><>now"`, `"release<<>now"`, `"release< now"`, its the same with any pair of symbols, if you have a single one of the pair then it's going to move – Memor-X Feb 21 '13 at 01:31
  • 1
    running this through 50k records should be lightening fast, I'd look into other issues if that is your problem. – user3486773 Apr 25 '17 at 20:31
24

If you know there won't be more than a certain number of spaces in a row, you could just nest the replace:

replace(replace(replace(replace(myText,'  ',' '),'  ',' '),'  ',' '),'  ',' ')

4 replaces should fix up to 16 consecutive spaces (16, then 8, then 4, then 2, then 1)

If it could be significantly longer, then you'd have to do something like an in-line function:

CREATE FUNCTION strip_spaces(@str varchar(8000))
RETURNS varchar(8000) AS
BEGIN 
    WHILE CHARINDEX('  ', @str) > 0 
        SET @str = REPLACE(@str, '  ', ' ')

    RETURN @str
END

Then just do

SELECT dbo.strip_spaces(myText) FROM myTable
BradC
  • 39,306
  • 13
  • 73
  • 89
  • Brad, I had almost identical code but you beat me to the Post, so upvote. Multiple REPLACE() calls are hackish, but if the number of expected "extra" spaces is predictable and relatively small, it'll do just fine and meet the OP's requirement not to call RegEx code via the CLR. – richardtallent Mar 16 '10 at 16:00
7

This is somewhat brute force, but will work

CREATE FUNCTION stripDoubleSpaces(@prmSource varchar(max)) Returns varchar(max)
AS 
BEGIN
    WHILE (PATINDEX('%  %', @prmSource)>0)
     BEGIN
        SET @prmSource = replace(@prmSource  ,'  ',' ')
     END

    RETURN @prmSource
END

GO

-- Unit test -- 
PRINT dbo.stripDoubleSpaces('single    spaces   only')

single spaces only
JohnFx
  • 34,542
  • 18
  • 104
  • 162
7

It can be done recursively via the function:

CREATE FUNCTION dbo.RemSpaceFromStr(@str VARCHAR(MAX)) RETURNS VARCHAR(MAX) AS
BEGIN
  RETURN (CASE WHEN CHARINDEX('  ', @str) > 0 THEN
    dbo.RemSpaceFromStr(REPLACE(@str, '  ', ' ')) ELSE @str END);
END

then, for example:

SELECT dbo.RemSpaceFromStr('some   string    with         many     spaces') AS NewStr

returns:

NewStr
some string with many spaces

Or the solution based on method described by @agdk26 or @Neil Knight (but safer)
both examples return output above:

SELECT REPLACE(REPLACE(REPLACE('some   string    with         many     spaces'
  , '  ', ' ' + CHAR(7)), CHAR(7) + ' ', ''), ' ' + CHAR(7), ' ') AS NewStr 
--but it remove CHAR(7) (Bell) from string if exists...

or

SELECT REPLACE(REPLACE(REPLACE('some   string    with         many     spaces'
  , '  ', ' ' + CHAR(7) + CHAR(7)), CHAR(7) + CHAR(7) + ' ', ''), ' ' + CHAR(7) + CHAR(7), ' ') AS NewStr
--but it remove CHAR(7) + CHAR(7) from string

How it works: enter image description here

Caution:
Char/string used to replace spaces shouldn't exist on begin or end of string and stand alone.

Community
  • 1
  • 1
Adam Silenko
  • 3,025
  • 1
  • 14
  • 30
7
update mytable
set myfield = replace (myfield, '  ',  ' ')
where charindex('  ', myfield) > 0 

Replace will work on all the double spaces, no need to put in multiple replaces. This is the set-based solution.

HLGEM
  • 94,695
  • 15
  • 113
  • 186
5

Here is a simple function I created for cleaning any spaces before or after, and multiple spaces within a string. It gracefully handles up to about 108 spaces in a single stretch and as many blocks as there are in the string. You can increase that by factors of 8 by adding additional lines with larger chunks of spaces if you need to. It seems to perform quickly and has not caused any problems in spite of it's generalized use in a large application.

CREATE FUNCTION [dbo].[fnReplaceMultipleSpaces] (@StrVal AS VARCHAR(4000)) 
RETURNS VARCHAR(4000) 
AS 
BEGIN

    SET @StrVal = Ltrim(@StrVal)
    SET @StrVal = Rtrim(@StrVal)

    SET @StrVal = REPLACE(@StrVal, '                ', ' ')  -- 16 spaces
    SET @StrVal = REPLACE(@StrVal, '        ', ' ')  -- 8 spaces
    SET @StrVal = REPLACE(@StrVal, '    ', ' ')  -- 4 spaces
    SET @StrVal = REPLACE(@StrVal, '  ', ' ')  -- 2 spaces
    SET @StrVal = REPLACE(@StrVal, '  ', ' ')  -- 2 spaces (for odd leftovers)

RETURN @StrVal

END
Tomasz Jakub Rup
  • 10,502
  • 7
  • 48
  • 49
3

Method #1

The first method is to replace extra spaces between words with an uncommon symbol combination as a temporary marker. Then you can replace the temporary marker symbols using the replace function rather than a loop.

Here is a code example that replaces text within a String variable.

DECLARE @testString AS VARCHAR(256) = ' Test        text   with  random*        spacing. Please normalize  this spacing!';
SELECT REPLACE(REPLACE(REPLACE(@testString, ' ', '*^'), '^*', ''), '*^', ' ');

Execution Time Test #1: In ten runs of this replacement method, the average wait time on server replies was 1.7 milliseconds and total execution time was 4.6 milliseconds. Execution Time Test #2: The average wait time on server replies was 1.7 milliseconds and total execution time was 3.7 milliseconds.

Method #2

The second method is not quite as elegant as the first, but also gets the job done. This method works by nesting four (or optionally more) replace statements that replace two blank spaces with one blank space.

DECLARE @testString AS VARCHAR(256) = ' Test        text   with  random*        spacing. Please normalize  this spacing!';
SELECT REPLACE(REPLACE(REPLACE(REPLACE(@testString,' ',' '),' ',' '),' ',' '),' ',' ')

Execution Time Test #1: In ten runs of this replacement method, the average wait time on server replies was 1.9 milliseconds and total execution time was 3.8 milliseconds. Execution Time Test #2: The average wait time on server replies was 1.8 milliseconds and total execution time was 4.8 milliseconds.

Method #3

The third method of replacing extra spaces between words is to use a simple loop. You can do a check on extra spaces in a while loop and then use the replace function to reduce the extra spaces with each iteration of the loop.

DECLARE @testString AS VARCHAR(256) = ' Test text with random* spacing. Please normalize this spacing!';
WHILE CHARINDEX(' ',@testString) > 0
SET @testString = REPLACE(@testString, ' ', ' ')
SELECT @testString

Execution Time Test #1: In ten runs of this replacement method, the average wait time on server replies was 1.8 milliseconds and total execution time was 3.4 milliseconds. Execution Time Test #2: The average wait time on server replies was 1.9 milliseconds and total execution time was 2.8 milliseconds.

2

You can try this:

select Regexp_Replace('single    spaces   only','( ){2,}', ' ') from dual;
Ori Dar
  • 18,687
  • 5
  • 58
  • 72
  • DECLARE @str varchar(150) SET @str='Hello Welcome to World of .net' Select REPLACE(REPLACE(REPLACE(@str,' ','{}'),'}{',''),'{}',' ') – Code May 17 '19 at 05:51
  • 2
    People have down voted this answer without providing an explanation. I believe it is because SQL Server does not have a native `Regexp_Replace()` function like Oracle does. [(reference)](https://stackoverflow.com/q/61488458/1898524) If your going to down vote please explain why so the author and future readers will learn from it. – Ben Feb 08 '21 at 23:49
2

Just Adding Another Method-

Replacing Multiple Spaces with Single Space WITHOUT Using REPLACE in SQL Server-

DECLARE @TestTable AS TABLE(input VARCHAR(MAX));

INSERT INTO @TestTable VALUES
('HAPPY         NEWYEAR     2020'),
('WELCOME       ALL     !');

SELECT
    CAST('<r><![CDATA[' + input + ']]></r>' AS XML).value('(/r/text())[1] cast as xs:token?','VARCHAR(MAX)')
    AS Expected_Result
FROM @TestTable;

--OUTPUT
/*
Expected_Result
HAPPY NEWYEAR 2020
WELCOME ALL !
*/
Arulmouzhi
  • 1,878
  • 17
  • 20
1

This is the solution via multiple replace, which works for any strings (does not need special characters, which are not part of the string).

declare @value varchar(max)
declare @result varchar(max)
set @value = 'alpha   beta gamma  delta       xyz'

set @result = replace(replace(replace(replace(replace(replace(replace(
  @value,'a','ac'),'x','ab'),'  ',' x'),'x ',''),'x',''),'ab','x'),'ac','a')

select @result -- 'alpha beta gamma delta xyz'
agdk26
  • 139
  • 1
  • 4
0

Found this while digging for an answer:

SELECT REPLACE(
        REPLACE(
             REPLACE(
                LTRIM(RTRIM('1 2  3   4    5     6'))
            ,'  ',' '+CHAR(7))
        ,CHAR(7)+' ','')
    ,CHAR(7),'') AS CleanString
where charindex('  ', '1 2  3   4    5     6') > 0

The full answer (with explanation) was pulled from: http://techtipsbysatish.blogspot.com/2010/08/sql-server-replace-multiple-spaces-with.html

On second look, seems to be just a slightly different version of the selected answer.

Limey
  • 2,642
  • 6
  • 37
  • 62
  • *On second look, seems to be just a slightly different version of the selected answer.* - should be deleted then. We don't need a hundred variations on the same theme.. – Caius Jard Oct 14 '21 at 05:46
0

Please Find below code

select trim(string_agg(value,' ')) from STRING_SPLIT('  single    spaces   only  ',' ')
where value<>' '

This worked for me.. Hope this helps...

Jaimil Patel
  • 1,301
  • 6
  • 13
  • 2
    Take note that the documentation actively states: *The output rows might be in any order. The order is not guaranteed to match the order of the substrings in the input string* - STRING_SPLIT does usually return sub strings in order of appearance in the input, but if it ever does return words in a different order, this method will effectively destroy the input string – Caius Jard Oct 14 '21 at 05:57
  • 1
    It is several years later, and now with SQL Server 2022, Azure SQL Database, Azure SQL Managed Instance, and Azure Synapse Analytics- you can use the ordinal parameter to match the order of strings. https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-ver16 – Zorkolot Apr 21 '23 at 13:05
0

With the "latest" SQL Server versions (Compatibility level 130) you could also use string_split and string_agg.

string_split can return an ordinal column when provided with a third argument. (https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-ver16#enable_ordinal). So we can preserve the order of the string_split.

Using a common table expression:

with cte(value) as (select value from string_split('  a b   c d     e     ', ' ', 1) where value <> '' order by ordinal offset 0 rows)
select string_agg(value, ' ') from cte

a b c d e results in a b c d e

cmxl
  • 663
  • 12
  • 24
-1

I use FOR XML PATH solution to replace multiple spaces into single space

The idea is to replace spaces with XML tags Then split XML string into string fragments without XML tags Finally concatenating those string values by adding single space characters between two

Here is how final UDF function can be called

select dbo.ReplaceMultipleSpaces('   Sample   text  with  multiple  space     ')
Eralper
  • 6,461
  • 2
  • 21
  • 27
  • This is a link-only answer that ceases to be useful if the linked resource dies. Copy the relevant parts of the link into your answer, please – Caius Jard Oct 14 '21 at 05:51