Aparar espaço em branco (espaços, guias, novas linhas)

10

Estou no SQL Server 2014 e preciso limpar o espaço em branco do início e do final do conteúdo de uma coluna, onde o espaço em branco pode ser simples, tabulação ou nova linha (ambos \ne \r\n); por exemplo

'    this content    '                          should become 'this content'
'  \r\n   \t\t\t this \r\n content \t  \r\n   ' should become 'this \r\n content'

e assim por diante.

Consegui alcançar apenas o primeiro caso com

UPDATE table t SET t.column = LTRIM(RTRIM(t.column))

mas para os outros casos, não funciona.

Giovanni Lovato
fonte

Respostas:

8

Para quem usa o SQL Server 2017 ou mais recente

você pode usar a função interna TRIM . Por exemplo:

DECLARE @Test NVARCHAR(4000);
SET @Test = N'  
    ' + NCHAR(0x09) + N'  ' + NCHAR(0x09) + N' this 
 ' + NCHAR(0x09) + NCHAR(0x09) + N'  content' + NCHAR(0x09) + NCHAR(0x09) + N'  
' + NCHAR(0x09) + N' ' + NCHAR(0x09) + NCHAR(0x09) + N'     ';

SELECT N'~'
        + TRIM(NCHAR(0x09) + NCHAR(0x20) + NCHAR(0x0D) + NCHAR(0x0A) FROM @Test)
        + N'~';

Observe que o comportamento padrão de TRIMé remover apenas espaços; portanto, para remover também as guias e as novas linhas (CR + LFs), é necessário especificar a characters FROMcláusula.

Além disso, usei NCHAR(0x09)os caracteres de tabulação na @Testvariável para que o código de exemplo possa ser copiado e colado e mantenha os caracteres corretos. Caso contrário, as guias serão convertidas em espaços quando esta página for renderizada.

Para quem usa o SQL Server 2016 ou mais antigo

Você pode criar uma função, como um UDF escalar SQLCLR ou um TVF embutido T-SQL (iTVF). O TVF embutido T-SQL seria o seguinte:

CREATE
--ALTER
FUNCTION dbo.TrimChars(@OriginalString NVARCHAR(4000), @CharsToTrim NVARCHAR(50))
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN
WITH cte AS
(
  SELECT PATINDEX(N'%[^' + @CharsToTrim + N']%', @OriginalString) AS [FirstChar],
         PATINDEX(N'%[^' + @CharsToTrim + N']%', REVERSE(@OriginalString)) AS [LastChar],
        LEN(@OriginalString + N'~') - 1 AS [ActualLength]
)
SELECT cte.[ActualLength],
       [FirstChar],
       ((cte.[ActualLength] - [LastChar]) + 1) AS [LastChar],
       SUBSTRING(@OriginalString, [FirstChar],
                 ((cte.[ActualLength] - [LastChar]) - [FirstChar] + 2)) AS [FixedString]
FROM   cte;
GO

E executando-o da seguinte maneira:

DECLARE @Test NVARCHAR(4000);
SET @Test = N'  
    ' + NCHAR(0x09) + N'  ' + NCHAR(0x09) + N' this 
 ' + NCHAR(0x09) + NCHAR(0x09) + N'  content' + NCHAR(0x09) + NCHAR(0x09) + N'  
' + NCHAR(0x09) + N' ' + NCHAR(0x09) + NCHAR(0x09) + N'     ';

SELECT N'~' + tc.[FixedString] + N'~' AS [proof]
FROM   dbo.TrimChars(@Test, NCHAR(0x09) + NCHAR(0x20) + NCHAR(0x0D) + NCHAR(0x0A)) tc;

Devoluções:

proof
----
~this 
              content~

E você pode usar isso em um UPDATEusando CROSS APPLY:

UPDATE tbl
SET    tbl.[Column] = itvf.[FixedString]
FROM   SchemaName.TableName tbl
CROSS APPLY  dbo.TrimChars(tbl.[Column],
                           NCHAR(0x09) + NCHAR(0x20) + NCHAR(0x0D) + NCHAR(0x0A)) itvf

Como mencionado no começo, isso também é muito fácil via SQLCLR, pois o .NET inclui um Trim()método que faz exatamente a operação que você deseja. Você pode codificar para chamar SqlString.Value.Trim()ou simplesmente instalar a versão gratuita da biblioteca SQL # (que eu criei, mas essa função está na versão gratuita) e usar String_Trim (que ocupa apenas espaço em branco) ou String_TrimChars em que você passa os caracteres para aparar dos dois lados (assim como o iTVF mostrado acima).

DECLARE @Test NVARCHAR(4000);
SET @Test = N'  
    ' + NCHAR(0x09) + N'  ' + NCHAR(0x09) + N' this 
 ' + NCHAR(0x09) + NCHAR(0x09) + N'  content' + NCHAR(0x09) + NCHAR(0x09) + N'  
' + NCHAR(0x09) + N' ' + NCHAR(0x09) + NCHAR(0x09) + N'     ';

SELECT N'~' + SQL#.String_Trim(@Test) + N'~' AS [proof];

E ele retorna exatamente a mesma string mostrada acima na saída de exemplo do iTVF. Mas, sendo um UDF escalar, você o usaria da seguinte maneira UPDATE:

UPDATE tbl
SET    tbl.[Column] = SQL#.String_Trim(itvf.[Column])
FROM   SchemaName.TableName tbl

Qualquer uma das opções acima deve ser eficiente para o uso em milhões de linhas. Os TVFs embutidos são otimizáveis, diferentemente dos TVFs com várias instruções e dos UDFs escalares T-SQL. Além disso, as UDFs escalares do SQLCLR têm o potencial de serem usadas em planos paralelos, desde que estejam marcadas IsDeterministic=truee não configurem nenhum tipo de DataAccess como Read(o padrão para o acesso aos dados do usuário e do sistema é None), e essas duas condições são true para ambas as funções SQLCLR mencionadas acima.

Solomon Rutzky
fonte
4

Você pode considerar o uso de um TVF (função com valor de tabela) para remover os caracteres incorretos do início e do fim dos seus dados.

Crie uma tabela para armazenar dados de teste:

IF COALESCE(OBJECT_ID('dbo.TrimTest'), 0) <> 0
BEGIN
    DROP TABLE dbo.TrimTest;
END
CREATE TABLE dbo.TrimTest
(
    SampleData VARCHAR(50) NOT NULL
);

INSERT INTO dbo.TrimTest (SampleData)
SELECT CHAR(13) + CHAR(10) + CHAR(9) + 'this is ' + CHAR(13) + CHAR(10) + ' a test' + CHAR(13) + CHAR(10);
GO

Crie o TVF:

IF COALESCE(OBJECT_ID('dbo.StripCrLfTab'), 0) <> 0
BEGIN
    DROP FUNCTION dbo.StripCrLfTab;
END
GO
CREATE FUNCTION dbo.StripCrLfTab
(
    @val NVARCHAR(1000)
)
RETURNS @Results TABLE
(
    TrimmedVal NVARCHAR(1000) NULL
)
AS
BEGIN
    DECLARE @TrimmedVal NVARCHAR(1000);
    SET @TrimmedVal = CASE WHEN RIGHT(@val, 1) = CHAR(13) OR RIGHT(@val, 1) = CHAR(10) OR RIGHT(@val, 1) = CHAR(9)
            THEN LEFT(
                CASE WHEN LEFT(@val, 1) = CHAR(13) OR LEFT(@val, 1) = CHAR(10) OR LEFT(@val, 1) = CHAR(9)
                THEN RIGHT(@val, LEN(@val) - 1)
                ELSE @val
                END
                , LEN(@val) -1 )
            ELSE
                CASE WHEN LEFT(@val, 1) = CHAR(13) OR LEFT(@val, 1) = CHAR(10) OR LEFT(@val, 1) = CHAR(9)
                THEN RIGHT(@val, LEN(@val) - 1)
                ELSE @val
                END
            END;
    IF @TrimmedVal LIKE (CHAR(13) + '%')
        OR @TrimmedVal LIKE (CHAR(10) + '%')
        OR @TrimmedVal LIKE (CHAR(9) + '%')
        OR @TrimmedVal LIKE ('%' + CHAR(13))
        OR @TrimmedVal LIKE ('%' + CHAR(10))
        OR @TrimmedVal LIKE ('%' + CHAR(9))
        SELECT @TrimmedVal = tv.TrimmedVal
        FROM dbo.StripCrLfTab(@TrimmedVal) tv;
    INSERT INTO @Results (TrimmedVal)
    VALUES (@TrimmedVal);
    RETURN;
END;
GO

Execute o TVF para mostrar os resultados:

SELECT tt.SampleData
    , stt.TrimmedVal
FROM dbo.TrimTest tt
CROSS APPLY dbo.StripCrLfTab(tt.SampleData) stt;

Resultados:

insira a descrição da imagem aqui

O TVF chama a si próprio recursivamente até que não haja caracteres ofensivos restantes no início e no final da sequência passada para a função. É improvável que tenha um bom desempenho em um grande número de linhas, mas provavelmente funcionaria bem se você estiver usando isso para corrigir dados à medida que são inseridos no banco de dados.

Você pode usar isso em uma declaração de atualização:

UPDATE dbo.TrimTest
SET TrimTest.SampleData = stt.TrimmedVal
FROM dbo.TrimTest tt
CROSS APPLY dbo.StripCrLfTab(tt.SampleData) stt;


SELECT *
FROM dbo.TrimTest;

Resultados (como texto):

insira a descrição da imagem aqui

Max Vernon
fonte
Obrigado Max, infelizmente eu tenho que limpar uma grande quantidade de linhas (milhões) em várias tabelas, esperava que alguma função fosse usada em uma UPDATEconsulta como LTRIM/ RTRIM, algo nas linhas de UPDATE table t SET t.column = TRIM(t.column, CONCAT(CHAR(9), CHAR(10), CHAR(13)))com uma TRIM( expression, charlist )função que aceita uma lista de caracteres para aparar como muitas linguagens de script possuem.
Giovanni Lovato
O aviso que dei sobre "provavelmente" não funcionar bem em muitas linhas pode ou não ser um problema. Se você estiver fazendo isso apenas uma vez, talvez não seja um problema. Você pode testá-lo em um ambiente que não seja de produção, para ver quanto tempo leva.
Max Vernon
Atualizarei minha resposta para mostrar como você usaria isso em uma updatedeclaração.
Max Vernon
1

Eu apenas tive um problema com essa situação específica, eu precisava encontrar e limpar todos os campos com espaços em branco, mas encontrei 4 tipos de espaços em branco possíveis nos campos do meu banco de dados (referência à tabela de códigos ASCII):

  • Guia Horizontal (caractere (9))
  • Nova linha (caractere (10))
  • Guia Vertical (caractere (9))
  • Espaço (caractere (32))

Talvez esta consulta possa ajudá-lo.

UPDATE @TABLE SET @COLUMN = replace(replace(replace(replace(@COLUMN,CHAR(9),''),CHAR(10),''),CHAR(13),''),CHAR(32),'')
sami.almasagedi
fonte
Isso limpa também os espaços em branco do meio dos campos, não apenas o início e o fim, conforme solicitado na pergunta.
Colin 'Hart
Sim, você está certo, eu vou editar
sami.almasagedi
-1

Você precisaria analisar o segundo exemplo, porque LTRIM / RTRIM apenas recortam espaços. Você realmente deseja aparar o que o SQL considera dados (/ r, / t, etc). Se você souber os valores que está procurando, use REPLACE para substituí-los. Melhor ainda, escreva uma função e chame-a.

Êxodo social
fonte
-1

Se quiser, use minha função elegante:

CREATE FUNCTION s_Trim
(
    @s nvarchar(max)
)
RETURNS nvarchar(max)
AS
BEGIN
    -- Create comparators for LIKE operator
    DECLARE @whitespaces nvarchar(50) = CONCAT('[ ', CHAR(9), CHAR(10), CHAR(13), ']'); -- Concat chars that you consider as whitespaces
    DECLARE @leftComparator nvarchar(50) = @whitespaces + '%',
            @rightComparator nvarchar(50) = '%' + @whitespaces;
    -- LTRIM
    WHILE @s LIKE @leftComparator AND LEN(@s + 'x') > 1 SET @s = RIGHT(@s, LEN(@s + 'x') - 2)
    -- RTRIM
    WHILE @s LIKE @rightComparator AND LEN(@s + 'x') > 1 SET @s = LEFT(@s, LEN(@s + 'x') - 2)

    RETURN @s;
END
GO
meehocz
fonte
11
Funções com valor escalar dificilmente são elegantes. Eles forçam as consultas a serem executadas em série e são executadas uma vez por linha (não uma vez por consulta). Você deve examinar as funções com valor de tabela embutida.
Erik Darling
-2

O uso da função em dados grandes pode levar muito tempo para ser executado. Eu tenho um conjunto de dados de 8 milhões de linhas, usando a função levou mais de 30 minutos para executar. replace(replace(replace(replace(@COLUMN,CHAR(9),''),CHAR(10),''),CHAR(13),''),CHAR(32),'')levou apenas 5 segundos. Obrigado a todos. Vejo você @ sami.almasagedi e @Colin 't Hart

Ábaco
fonte
Como na resposta que você está repetindo, isso não resolve o problema se for necessário reter espaços em branco entre o primeiro e o último caractere que não sejam espaços em branco. A velocidade só é útil quando resulta na resposta desejada. Além disso - consulte as notas na resposta aceita sobre como garantir que as funções não diminuem a velocidade de uma consulta como esta.
RDFozz