web-dev-qa-db-fra.com

Motif de regex dans SQL Remplacer la fonction?

SELECT REPLACE('<strong>100</strong><b>.00 GB', '%^(^-?\d*\.{0,1}\d+$)%', '');

Je veux remplacer n'importe quel balisage entre deux parties du nombre par la regex ci-dessus, mais cela ne semble pas fonctionner. Je ne sais pas si c'est la syntaxe regex qui est fausse, car j'ai essayé une méthode plus simple, telle que '%[^0-9]%', juste pour tester, mais cela n'a pas fonctionné non plus. Est-ce que quelqu'un sait comment puis-je y parvenir?

59
JanT

Vous pouvez utiliser PATINDEX Pour trouver le premier index de l'occurrence du modèle (chaîne). Ensuite, utilisez STUFF pour insérer une autre chaîne dans le motif (chaîne) correspondant.

Boucle à travers chaque ligne. Remplacez chaque caractère illégal par ce que vous voulez. Dans votre cas, remplacez non numérique par un blanc. La boucle interne est si vous avez plus d’un caractère illégal dans une cellule actuelle celui de la boucle.

DECLARE @counter int

SET @counter = 0

WHILE(@counter < (SELECT MAX(ID_COLUMN) FROM Table))
BEGIN  

    WHILE 1 = 1
    BEGIN
        DECLARE @RetVal varchar(50)

        SET @RetVal =  (SELECT Column = STUFF(Column, PATINDEX('%[^0-9.]%', Column),1, '')
        FROM Table
        WHERE ID_COLUMN = @counter)

        IF(@RetVal IS NOT NULL)       
          UPDATE Table SET
          Column = @RetVal
          WHERE ID_COLUMN = @counter
        ELSE
            break
    END

    SET @counter = @counter + 1
END

Attention: c'est lent quand même! Avoir une colonne varchar peut avoir un impact. Donc, utiliser LTRIM RTRIM peut aider un peu. Peu importe, c'est lent.

Le crédit va à this StackOverFlow answer.

EDITCredit va également à @srutzky

Éditer (par @Tmdean) Au lieu d’effectuer une ligne à la fois, cette réponse peut être adaptée à une solution davantage basée sur les ensembles. Il itère toujours le maximum du nombre de caractères non numériques dans une seule ligne, ce n'est donc pas idéal, mais je pense que cela devrait être acceptable dans la plupart des situations.

WHILE 1 = 1 BEGIN
    WITH q AS
        (SELECT ID_Column, PATINDEX('%[^0-9.]%', Column) AS n
        FROM Table)
    UPDATE Table
    SET Column = STUFF(Column, q.n, 1, '')
    FROM q
    WHERE Table.ID_Column = q.ID_Column AND q.n != 0;

    IF @@ROWCOUNT = 0 BREAK;
END;

Vous pouvez également améliorer beaucoup l'efficacité si vous conservez une colonne de bits dans le tableau indiquant si le champ a déjà été nettoyé. (NULL représente "Inconnu" dans mon exemple et devrait être la colonne par défaut.)

DECLARE @done bit = 0;
WHILE @done = 0 BEGIN
    WITH q AS
        (SELECT ID_Column, PATINDEX('%[^0-9.]%', Column) AS n
        FROM Table
        WHERE COALESCE(Scrubbed_Column, 0) = 0)
    UPDATE Table
    SET Column = STUFF(Column, q.n, 1, ''),
        Scrubbed_Column = 0
    FROM q
    WHERE Table.ID_Column = q.ID_Column AND q.n != 0;

    IF @@ROWCOUNT = 0 SET @done = 1;

    -- if Scrubbed_Column is still NULL, then the PATINDEX
    -- must have given 0
    UPDATE table
    SET Scrubbed_Column = CASE
        WHEN Scrubbed_Column IS NULL THEN 1
        ELSE NULLIF(Scrubbed_Column, 0)
    END;
END;

Si vous ne souhaitez pas modifier votre schéma, il est facile de l'adapter pour stocker les résultats intermédiaires dans une variable de valeur de table qui est appliquée à la table réelle à la fin.

48
Mukus

De manière générale, SQL Server ne prend pas en charge les expressions régulières et vous ne pouvez pas les utiliser dans le code T-SQL natif. 

Vous pourriez écrire une fonction CLR pour le faire. Voir ici , par exemple.

21
Szymon

Au lieu de supprimer le personnage trouvé par sa seule position, l'utilisation de Replace(Column, BadFoundCharacter, '') pourrait être considérablement plus rapide. En outre, au lieu de simplement remplacer le caractère incorrect suivant dans chaque colonne, il remplace tous ceux trouvés.

WHILE 1 = 1 BEGIN
    UPDATE dbo.YourTable
    SET Column = Replace(Column, Substring(Column, PatIndex('%[^0-9.-]%', Column), 1), '')
    WHERE Column LIKE '%[^0-9.-]%'
    If @@RowCount = 0 BREAK;
END;

Je suis convaincu que cela fonctionnera mieux que la réponse acceptée, ne serait-ce que parce que cela fait moins d'opérations. Il y a d'autres moyens qui pourraient aussi être plus rapides, mais je n'ai pas le temps de les explorer maintenant.

15
ErikE

Je suis tombé par hasard sur ce post à la recherche de quelque chose d'autre, mais je pensais mentionner une solution que j'utilise qui est beaucoup plus efficace - et qui devrait être l'implémentation par défaut de toute fonction lorsqu'elle est utilisée avec une requête basée sur un ensemble - qui consiste à appliquer une croix fonction de table. On dirait que le sujet est toujours actif alors j'espère que cela sera utile à quelqu'un.

Exemple d'exécution sur quelques réponses jusqu'à présent, basé sur l'exécution de requêtes ou de fonctions scalaires basées sur des ensembles récursifs, basé sur un ensemble de tests de 1 m lignes supprimant les caractères d'un nouvel identifiant aléatoire forever} pour les exemples de fonctions.

L'utilisation d'une fonction de tableau avec application croisée permet d'atteindre le même objectif en 10s . Vous devrez peut-être l’ajuster en fonction de vos besoins, comme la longueur maximale qu’il gère.

Une fonction:

CREATE FUNCTION [dbo].[RemoveChars](@InputUnit VARCHAR(40))
RETURNS TABLE
AS
RETURN
    (
        WITH Numbers_prep(Number) AS
            (
                SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
            )
        ,Numbers(Number) AS
            (
                SELECT TOP (ISNULL(LEN(@InputUnit),0))
                    row_number() OVER (ORDER BY (SELECT NULL))
                FROM Numbers_prep a
                    CROSS JOIN Numbers_prep b
            )
        SELECT
            OutputUnit
        FROM
            (
                SELECT
                    substring(@InputUnit,Number,1)
                FROM  Numbers
                WHERE substring(@InputUnit,Number,1) like '%[0-9]%'
                ORDER BY Number
                FOR XML PATH('')
            ) Sub(OutputUnit)
    )

Usage:

UPDATE t
SET column = o.OutputUnit
FROM ##t t
CROSS APPLY [dbo].[RemoveChars](t.column) o
3
SQLGobbleDeGook

Voici une fonction récursive que j'ai écrite pour accomplir cela à partir des réponses précédentes.

CREATE FUNCTION dbo.RecursiveReplace
(
    @P_String VARCHAR(MAX),
    @P_Pattern VARCHAR(MAX),
    @P_ReplaceString VARCHAR(MAX),
    @P_ReplaceLength INT = 1
)
RETURNS VARCHAR(MAX)
BEGIN
    DECLARE @Index INT;

    -- Get starting point of pattern
    SET @Index = PATINDEX(@P_Pattern, @P_String);

    IF @Index > 0
    BEGIN
        -- Perform the replace
        SET @P_String = STUFF(@P_String, PATINDEX(@P_Pattern, @P_String), @P_ReplaceLength, @P_ReplaceString);

        -- Recurse
        SET @P_String = dbo.RecursiveReplace(@P_String, @P_Pattern, @P_ReplaceString, @P_ReplaceLength);
    END;

    RETURN @P_String;
END;

Essentiel

3
jkdba

Intégrer la solution dans une fonction SQL pourrait être utile si vous souhaitez la réutiliser . Je le fais même au niveau de la cellule, c'est pourquoi je propose une réponse différente:

CREATE FUNCTION [dbo].[fnReplaceInvalidChars] (@string VARCHAR(300))
RETURNS VARCHAR(300)
BEGIN
    DECLARE @str VARCHAR(300) = @string;
    DECLARE @Pattern VARCHAR (20) = '%[^a-zA-Z0-9]%';
    DECLARE @Len INT;
    SELECT @Len = LEN(@String); 
    WHILE @Len > 0 
    BEGIN
        SET @Len = @Len - 1;
        IF (PATINDEX(@Pattern,@str) > 0)
            BEGIN
                SELECT @str = STUFF(@str, PATINDEX(@Pattern,@str),1,'');    
            END
        ELSE
        BEGIN
            BREAK;
        END
    END     
    RETURN @str
END
2
Ivan Rascon

Si vous faites cela uniquement pour un paramètre entrant dans une procédure stockée, vous pouvez utiliser les éléments suivants:

while PatIndex('%[^0-9]%', @Param) > 0
    select  @Param = Replace(@Param, Substring(@Param, PatIndex('%[^0-9]%', @Param), 1), '')
1
goddess_elli

Je pense qu'une approche plus simple et plus rapide consiste à parcourir chaque caractère de l'alphabet:

DECLARE @i int
SET @i = 0

WHILE(@i < 256)
BEGIN  

    IF char(@i) NOT IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.')      

      UPDATE Table SET Column = replace(Column, char(@i), '')

    SET @i = @i + 1

END
0
Gregorio

J'ai créé cette fonction pour nettoyer une chaîne contenant des caractères non numériques dans un champ time. L'heure contenait des points d'interrogation quand ils n'ont pas ajouté les procès-verbaux, quelque chose comme ceci: 20: ??. La fonction parcourt chaque caractère et remplace le? avec un 0:

 CREATE FUNCTION [dbo].[CleanTime]
(
    -- Add the parameters for the function here
    @intime nvarchar(10) 
)
RETURNS nvarchar(5)
AS
BEGIN
    -- Declare the return variable here
    DECLARE @ResultVar nvarchar(5)
    DECLARE @char char(1)
    -- Add the T-SQL statements to compute the return value here
    DECLARE @i int = 1
    WHILE @i <= LEN(@intime)
    BEGIN
    SELECT @char =  CASE WHEN substring(@intime,@i,1) like '%[0-9:]%' THEN substring(@intime,@i,1) ELSE '0' END
    SELECT @ResultVar = concat(@ResultVar,@char)   
    set @i  = @i + 1       
    END;
    -- Return the result of the function
    RETURN @ResultVar

END
0
Nordin