web-dev-qa-db-fra.com

Moyen le plus rapide de supprimer les caractères non numériques d'un VARCHAR dans SQL Server

J'écris un utilitaire d'importation qui utilise les numéros de téléphone comme clé unique dans l'importation.

Je dois vérifier que le numéro de téléphone n'existe pas déjà dans ma base de données. Le problème est que les numéros de téléphone dans la base de données peuvent comporter des traits tels que des tirets, des parenthèses et éventuellement d'autres éléments. J'ai écrit une fonction pour supprimer ces éléments, le problème est qu'il s'agit de slow et que des milliers d'enregistrements dans ma base de données et des milliers d'enregistrements doivent être importés simultanément, ce processus peut être extrêmement lent. J'ai déjà fait de la colonne de numéro de téléphone un index.

J'ai essayé d'utiliser le script de ce post:
T-SQL trim & nbsp (et autres caractères non alphanumériques)

Mais cela n'a pas accéléré le tout.

Existe-t-il un moyen plus rapide de supprimer les caractères non numériques? Quelque chose qui peut bien fonctionner lorsque 10 000 à 100 000 enregistrements doivent être comparés.

Tout ce qui est fait doit être exécuté fast.

Mettre à jour
Compte tenu de ce que les gens ont répondu, je pense que je vais devoir nettoyer les champs avant d'exécuter l'utilitaire d'importation. 

Pour répondre à la question dans laquelle j'écris l'utilitaire d'importation, il s'agit d'une application C #. Je compare maintenant BIGINT à BIGINT, sans qu'il soit nécessaire de modifier les données de base de données, et je continue de subir des pertes de performances avec un très petit ensemble de données (environ 2 000 enregistrements). 

Est-ce que comparer BIGINT à BIGINT pourrait ralentir les choses?

J'ai optimisé le plus possible le côté code de mon application (expressions rationnelles supprimées, appels supprimés de bases de données inutiles). Bien que je ne puisse plus isoler SQL comme source du problème, je me sens toujours comme ça.

59
Dan Herbert

Je peux mal comprendre, mais vous avez deux ensembles de données pour supprimer les chaînes d'une chaîne pour les données actuelles de la base de données, puis un nouvel ensemble chaque fois que vous importez.

Pour mettre à jour les enregistrements existants, je voudrais simplement utiliser SQL, cela ne doit se produire qu'une seule fois.

Cependant, SQL n'est pas optimisé pour ce type d'opération, puisque vous avez écrit un utilitaire d'importation, je ferais ces mises à jour dans le contexte de l'utilitaire d'importation lui-même, pas en SQL. Ce serait bien mieux en termes de performances. En quoi vous écrivez l'utilitaire?

De plus, je peux avoir complètement mal compris le processus, alors je m'excuse si je ne suis pas à la base.

Modifier: 
Pour la mise à jour initiale, si vous utilisez SQL Server 2005, vous pouvez essayer une fonction CLR. Voici une rapide en utilisant regex. Pas sûr de savoir comment comparer les performances, je ne l'ai jamais utilisé moi-même, sauf pour un test rapide pour le moment.

using System;  
using System.Data;  
using System.Text.RegularExpressions;  
using System.Data.SqlClient;  
using System.Data.SqlTypes;  
using Microsoft.SqlServer.Server;  

public partial class UserDefinedFunctions  
{  
    [Microsoft.SqlServer.Server.SqlFunction]  
    public static SqlString StripNonNumeric(SqlString input)  
    {  
        Regex regEx = new Regex(@"\D");  
        return regEx.Replace(input.Value, "");  
    }  
};  

Une fois ceci déployé, vous pouvez simplement utiliser:

UPDATE table SET phoneNumber = dbo.StripNonNumeric(phoneNumber)
15
Scott Nichols

J'ai vu cette solution avec le code T-SQL et PATINDEX. Je l'aime :-)

CREATE Function [fnRemoveNonNumericCharacters](@strText VARCHAR(1000))
RETURNS VARCHAR(1000)
AS
BEGIN
    WHILE PATINDEX('%[^0-9]%', @strText) > 0
    BEGIN
        SET @strText = STUFF(@strText, PATINDEX('%[^0-9]%', @strText), 1, '')
    END
    RETURN @strText
END
102
David Coster

replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(string,'a',''),'b',''),'c',''),'d',''),'e',''),'f',''),'g',''),'h',''),'i',''),'j',''),'k',''),'l',''),'m',''),'n',''),'o',''),'p',''),'q',''),'r',''),'s',''),'t',''),'u',''),'v',''),'w',''),'x',''),'y',''),'z',''),'A',''),'B',''),'C',''),'D',''),'E',''),'F',''),'G',''),'H',''),'I',''),'J',''),'K',''),'L',''),'M',''),'N',''),'O',''),'P',''),'Q',''),'R',''),'S',''),'T',''),'U',''),'V',''),'W',''),'X',''),'Y',''),'Z','')*1 AS string,

:)

36
Brainwater

Si vous ne souhaitez pas créer de fonction ou si vous n'avez besoin que d'un seul appel en ligne dans T-SQL, vous pouvez essayer:

set @Phone = REPLACE(REPLACE(REPLACE(REPLACE(@Phone,'(',''),' ',''),'-',''),')','')

Bien sûr, cela est spécifique à la suppression du formatage du numéro de téléphone, pas à un générique supprimer tous les caractères spéciaux de la fonction chaîne.

16
Tom

Fonction simple:

CREATE FUNCTION [dbo].[RemoveAlphaCharacters](@InputString VARCHAR(1000))
RETURNS VARCHAR(1000)
AS
BEGIN
  WHILE PATINDEX('%[^0-9]%',@InputString)>0
        SET @InputString = STUFF(@InputString,PATINDEX('%[^0-9]%',@InputString),1,'')     
  RETURN @InputString
END

GO
9
AdamE
create function dbo.RemoveNonNumericChar(@str varchar(500))  
returns varchar(500)  
begin  
declare @startingIndex int  
set @startingIndex=0  
while 1=1  
begin  
    set @startingIndex= patindex('%[^0-9]%',@str)  
    if @startingIndex <> 0  
    begin  
        set @str = replace(@str,substring(@str,@startingIndex,1),'')  
    end  
    else    break;   
end  
return @str  
end

go  

select dbo.RemoveNonNumericChar('aisdfhoiqwei352345234@#$%^$@345345%^@#$^')  
6
Debayan Samaddar

Travailler avec varchars est fondamentalement lent et inefficace comparé au travail numérique, pour des raisons évidentes. Les fonctions que vous associez à l'article original seront en effet assez lentes, car elles parcourent chaque caractère de la chaîne pour déterminer s'il s'agit ou non d'un nombre. Faites cela pour des milliers d'enregistrements et le processus est forcément lent. C'est le travail idéal pour les expressions régulières, mais elles ne sont pas prises en charge de manière native dans SQL Server. Vous pouvez ajouter un support en utilisant une fonction CLR, mais il est difficile de dire à quel point cela sera lent sans l'essayer. Je m'attendrais certainement à ce que ce soit beaucoup plus rapide que de parcourir en boucle chaque caractère de chaque numéro de téléphone!

Une fois que vous avez formaté les numéros de téléphone dans votre base de données pour qu'ils ne soient plus que des chiffres, vous pouvez passer à un type numérique SQL qui permettrait des comparaisons ultra-rapides avec d'autres types numériques. Vous constaterez peut-être que, selon la rapidité avec laquelle vos nouvelles données arrivent, effectuer le découpage et la conversion au format numérique du côté base de données est suffisamment rapide une fois que vous comparez est correctement mis en forme, mais si possible, il serait préférable éteindre l'écriture d'un utilitaire d'importation dans un langage .NET qui s'occuperait de ces problèmes de formatage avant de toucher à la base de données.

Quoi qu'il en soit, vous allez avoir un gros problème en ce qui concerne le formatage optionnel. Même s'il est garanti que vos numéros sont uniquement d'origine nord-américaine, certaines personnes placent le 1 devant un numéro de téléphone entièrement qualifié, tandis que d'autres ne le feront pas, ce qui entraînera le risque d'entrées multiples du même numéro de téléphone. De plus, en fonction de ce que vos données représentent, certaines personnes utiliseront leur numéro de téléphone à la maison, qui peut y héberger plusieurs personnes. Une contrainte unique ne permet donc qu'un seul membre de la base de données par ménage. Certains utiliseraient leur numéro de travail et auraient le même problème, et d’autres incluraient ou non l’extension qui créerait à nouveau un potentiel d’unicité artificielle.

Tout cela peut ou non avoir un impact sur vous, en fonction de vos données et de vos usages, mais il est important de garder à l'esprit!

1
Grank

Je voudrais d'abord essayer la fonction CLR de Scott, mais ajouter une clause WHERE pour réduire le nombre d'enregistrements mis à jour.

UPDATE table SET phoneNumber = dbo.StripNonNumeric(phoneNumber) 
WHERE phonenumber like '%[^0-9]%'

Si vous savez que la grande majorité de vos enregistrements ont des caractères non numériques, cela ne vous aidera peut-être pas.

1
Mike L

pouvez-vous les supprimer lors d'un processus nocturne, les stocker dans un champ séparé, puis effectuer une mise à jour des enregistrements modifiés juste avant l'exécution du processus?

Ou sur l'insertion/mise à jour, stockez le format "numérique", pour faire référence plus tard. Un déclencheur serait un moyen facile de le faire.

1
Dan Williams

Je sais que le jeu est en retard, mais voici une fonction que j'ai créée pour T-SQL qui supprime rapidement les caractères non numériques. À noter que j'ai un schéma "String" dans lequel je mets des fonctions utilitaires pour les chaînes ...

CREATE FUNCTION String.ComparablePhone( @string nvarchar(32) ) RETURNS bigint AS
BEGIN
    DECLARE @out bigint;

-- 1. table of unique characters to be kept
    DECLARE @keepers table ( chr nchar(1) not null primary key );
    INSERT INTO @keepers ( chr ) VALUES (N'0'),(N'1'),(N'2'),(N'3'),(N'4'),(N'5'),(N'6'),(N'7'),(N'8'),(N'9');

-- 2. Identify the characters in the string to remove
    WITH found ( id, position ) AS
    (
        SELECT 
            ROW_NUMBER() OVER (ORDER BY (n1+n10) DESC), -- since we are using stuff, for the position to continue to be accurate, start from the greatest position and work towards the smallest
            (n1+n10)
        FROM 
            (SELECT 0 AS n1 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS d1,
            (SELECT 0 AS n10 UNION SELECT 10 UNION SELECT 20 UNION SELECT 30) AS d10
        WHERE
            (n1+n10) BETWEEN 1 AND len(@string)
            AND substring(@string, (n1+n10), 1) NOT IN (SELECT chr FROM @keepers)
    )
-- 3. Use stuff to snuff out the identified characters
    SELECT 
        @string = stuff( @string, position, 1, '' )
    FROM 
        found
    ORDER BY
        id ASC; -- important to process the removals in order, see ROW_NUMBER() above

-- 4. Try and convert the results to a bigint   
    IF len(@string) = 0
        RETURN NULL; -- an empty string converts to 0

    RETURN convert(bigint,@string); 
END

Puis l'utiliser pour comparer pour l'insertion, quelque chose comme ceci;

INSERT INTO Contacts ( phone, first_name, last_name )
SELECT i.phone, i.first_name, i.last_name
FROM Imported AS i
LEFT JOIN Contacts AS c ON String.ComparablePhone(c.phone) = String.ComparablePhone(i.phone)
WHERE c.phone IS NULL -- Exclude those that already exist
1
Dennis Allen

J'utiliserais une fonction intégrée du point de vue des performances, voir ci-dessous: Notez que les symboles tels que '+', '-' etc. ne seront pas supprimés

CREATE FUNCTION [dbo].[UDF_RemoveNumericStringsFromString]
 (
 @str varchar(100)
 )
 RETURNS TABLE AS RETURN
 WITH Tally (n) as 
  (
  -- 100 rows
   SELECT TOP (Len(@Str)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
   FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
   CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
  )

  SELECT OutStr =  STUFF(
       (SELECT SUBSTRING(@Str, n,1) st
        FROM Tally
        WHERE ISNUMERIC(SUBSTRING(@Str, n,1)) = 1
        FOR XML PATH(''),type).value('.', 'varchar(100)'),1,0,'')
  GO

  /*Use it*/
  SELECT OutStr
  FROM dbo.UDF_RemoveNumericStringsFromString('fjkfhk759734977fwe9794t23')
  /*Result set
   759734977979423 */

Vous pouvez le définir avec plus de 100 caractères ...

0
hkravitz

Des milliers d'enregistrements contre des milliers d'enregistrements ne posent normalement pas de problème. J'ai utilisé SSIS pour importer des millions d'enregistrements avec de la duplication comme ceci.

Je voudrais nettoyer la base de données pour supprimer les caractères non numériques en premier lieu et les garder.

0
Cade Roux

Vous recherchez une solution super simple:

SUBSTRING([Phone], CHARINDEX('(', [Phone], 1)+1, 3)
       + SUBSTRING([Phone], CHARINDEX(')', [Phone], 1)+1, 3)
       + SUBSTRING([Phone], CHARINDEX('-', [Phone], 1)+1, 4) AS Phone
0
Tim

"Bien que je ne puisse plus isoler SQL comme source du problème, je me sens toujours comme ça."

Lancez SQL Profiler et jetez un coup d'oeil. Prenez les requêtes résultantes et vérifiez leurs plans d'exécution pour vous assurer que l'index est utilisé.

0
Amy B