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.
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)
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
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
,
:)
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.
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
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%^@#$^')
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!
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.
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.
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
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 ...
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.
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
"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é.