web-dev-qa-db-fra.com

Rechercher des caractères non ASCII dans les colonnes varchar à l'aide de SQL Server

Comment renvoyer des lignes avec des caractères non ASCII à l'aide de SQL Server?
Si vous pouvez montrer comment le faire pour une colonne, ce serait bien.

Je fais quelque chose comme ça maintenant, mais ça ne marche pas

select *
from Staging.APARMRE1 as ar
where ar.Line like '%[^!-~ ]%'

Pour un crédit supplémentaire, s'il peut s'étendre sur toutes les varchar colonnes d'un tableau, ce serait exceptionnel! Dans cette solution, ce serait bien de renvoyer trois colonnes:

  • Le champ d'identité de cet enregistrement. (Cela permettra de revoir l'ensemble de l'enregistrement avec une autre requête.)
  • Le nom de la colonne
  • Le texte avec le caractère invalide
 Id | FieldName | InvalidText       |
----+-----------+-------------------+
 25 | LastName  | Solís             |
 56 | FirstName | François          |
100 | Address1  | 123 Ümlaut street |

Les caractères non valides seraient en dehors de la plage de SPACE (32dix) à travers ~ (127dix)

50
Gerhard Weiss

Voici une solution pour la recherche sur une seule colonne à l'aide de PATINDEX.
Il affiche également StartPosition, InvalidCharacter et ASCII code.

select line,
  patindex('%[^ !-~]%' COLLATE Latin1_General_BIN,Line) as [Position],
  substring(line,patindex('%[^ !-~]%' COLLATE Latin1_General_BIN,Line),1) as [InvalidCharacter],
  ascii(substring(line,patindex('%[^ !-~]%' COLLATE Latin1_General_BIN,Line),1)) as [ASCIICode]
from  staging.APARMRE1
where patindex('%[^ !-~]%' COLLATE Latin1_General_BIN,Line) >0
70
Gerhard Weiss

Ce script recherche les caractères non ascii dans une colonne. Il génère une chaîne de tous les caractères valides, ici les points de code 32 à 127. Ensuite, il recherche les lignes qui ne correspondent pas à la liste:

declare @str varchar(128)
declare @i int
set @str = ''
set @i = 32
while @i <= 127
    begin
    set @str = @str + '|' + char(@i)
    set @i = @i + 1
    end

select  col1
from    YourTable
where   col1 like '%[^' + @str + ']%' escape '|'
13
Andomar

J'ai exécuté ce morceau de code avec succès

declare @UnicodeData table (
     data nvarchar(500)
)
insert into 
    @UnicodeData
values 
    (N'Horse�')
    ,(N'Dog')
    ,(N'Cat')

select
    data
from
    @UnicodeData 
where
    data collate LATIN1_GENERAL_BIN != cast(data as varchar(max))

Ce qui fonctionne bien pour les colonnes connues.

Pour un crédit supplémentaire, j'ai écrit ce script rapide pour rechercher toutes les colonnes nvarchar dans une table donnée pour les caractères Unicode.

declare 
    @sql    varchar(max)    = ''
    ,@table sysname         = 'mytable' -- enter your table here

;with ColumnData as (
    select
        RowId               = row_number() over (order by c.COLUMN_NAME)
        ,c.COLUMN_NAME
        ,ColumnName         = '[' + c.COLUMN_NAME + ']'
        ,TableName          = '[' + c.TABLE_SCHEMA + '].[' + c.TABLE_NAME + ']' 
    from
        INFORMATION_SCHEMA.COLUMNS c
    where
        c.DATA_TYPE         = 'nvarchar'
        and c.TABLE_NAME    = @table
)
select
    @sql = @sql + 'select FieldName = ''' + c.ColumnName + ''',         InvalidCharacter = [' + c.COLUMN_NAME + ']  from ' + c.TableName + ' where ' + c.ColumnName + ' collate LATIN1_GENERAL_BIN != cast(' + c.ColumnName + ' as varchar(max)) '  +  case when c.RowId <> (select max(RowId) from ColumnData) then  ' union all ' else '' end + char(13)
from
    ColumnData c

-- check
-- print @sql
exec (@sql)

Je ne suis pas un fan de SQL dynamique, mais il a ses utilisations pour des requêtes exploratoires comme celle-ci.

12
Vash

exécutant les différentes solutions sur certaines données du monde réel - 12M lignes varchar longueur ~ 30, environ 9k lignes douteuses, pas d'index de texte intégral en jeu, la solution patIndex est la plus rapide, et elle sélectionne également le plus de lignes.

(km pré-exécuté pour régler le cache sur un état connu, a exécuté les 3 processus et a finalement exécuté à nouveau le km - les 2 derniers parcours de km ont donné des temps dans les 2 secondes)

solution patindex de Gerhard Weiss - Runtime 0:38, retourne 9144 lignes

select dodgyColumn from myTable fcc
WHERE  patindex('%[^ !-~]%' COLLATE Latin1_General_BIN,dodgyColumn ) >0

la solution sous-chaîne-nombres par MT. - Runtime 1:16, a renvoyé 8996 lignes

select dodgyColumn from myTable fcc
INNER JOIN dbo.Numbers32k dn ON dn.number<(len(fcc.dodgyColumn ))
WHERE ASCII(SUBSTRING(fcc.dodgyColumn , dn.Number, 1))<32 
    OR ASCII(SUBSTRING(fcc.dodgyColumn , dn.Number, 1))>127

solution udf par Deon Robertson - Runtime 3:47, renvoie 7316 lignes

select dodgyColumn 
from myTable 
where dbo.udf_test_ContainsNonASCIIChars(dodgyColumn , 1) = 1
6
Andrew Hill

Une fonction définie par l'utilisateur est disponible sur le Web "Analyser alphanumérique". Google UDF analyse les caractères alphanumériques et vous devriez trouver le code correspondant. Cette fonction définie par l'utilisateur supprime tous les caractères qui ne se situent pas entre 0-9, a-z et A-Z.

Select * from Staging.APARMRE1 ar
where udf_parsealpha(ar.last_name) <> ar.last_name

Cela devrait ramener tous les enregistrements qui ont un nom de famille avec des caractères non valides pour vous ... bien que votre question sur les points bonus soit un peu plus difficile, mais je pense qu'une déclaration de cas pourrait le gérer. Ceci est un peu le code pseudo, je ne suis pas tout à fait sûr si cela fonctionnerait.

Select id, case when udf_parsealpha(ar.last_name) <> ar.last_name then 'last name'
when udf_parsealpha(ar.first_name) <> ar.first_name then 'first name'
when udf_parsealpha(ar.Address1) <> ar.last_name then 'Address1'
end, 
case when udf_parsealpha(ar.last_name) <> ar.last_name then ar.last_name
when udf_parsealpha(ar.first_name) <> ar.first_name then ar.first_name
when udf_parsealpha(ar.Address1) <> ar.last_name then ar.Address1
end
from Staging.APARMRE1 ar
where udf_parsealpha(ar.last_name) <> ar.last_name or
udf_parsealpha(ar.first_name) <> ar.first_name or
udf_parsealpha(ar.Address1) <> ar.last_name 

J'ai écrit ceci dans la boîte aux lettres du forum ... donc je ne sais pas trop si cela fonctionnera tel quel, mais cela devrait être proche. Je ne sais pas trop comment il se comportera si un seul enregistrement a deux champs avec des caractères non valides.

Vous pouvez également remplacer la clause from d'une table unique par une sous-requête qui ressemble à quelque chose comme:

select id,fieldname,value from (
Select id,'last_name' as 'fieldname', last_name as 'value'
from Staging.APARMRE1 ar
Union
Select id,'first_name' as 'fieldname', first_name as 'value'
from Staging.APARMRE1 ar
---(and repeat unions for each field)
)
where udf_parsealpha(value) <> value

L'avantage ici est que pour chaque colonne, vous n'aurez qu'à étendre la déclaration d'union ici, tandis que vous devez mettre cette comparaison trois fois pour chaque colonne dans la version de déclaration de cas de ce script

3
Twelfth

Pour trouver quel champ contient des caractères non valides:

SELECT * FROM Staging.APARMRE1 FOR XML AUTO, TYPE

Vous pouvez le tester avec cette requête:

SELECT top 1 'char 31: '+char(31)+' (hex 0x1F)' field
from sysobjects
FOR XML AUTO, TYPE

Le résultat sera:

Msg 6841, niveau 16, état 1, ligne 3 pour XML n'a pas pu sérialiser les données pour le "champ" de noeud car il contient un caractère (0x001F) qui n'est pas autorisé en XML. Pour récupérer ces données à l'aide de FOR XML, convertissez-les en type de données binaire, varbinaire ou image et utilisez la directive BINARY BASE64.

Il est très utile lorsque vous écrivez des fichiers xml et obtenez une erreur de caractères invalides lors de la validation.

2
lynx_74

Voici un UDF I construit pour détecter les colonnes avec des caractères ascii étendus. C'est rapide et vous pouvez étendre le jeu de caractères que vous souhaitez vérifier. Le deuxième paramètre vous permet de basculer entre la vérification de tout ce qui est en dehors du jeu de caractères standard ou l'autorisation d'un jeu étendu:

create function [dbo].[udf_ContainsNonASCIIChars]
(
@string nvarchar(4000),
@checkExtendedCharset bit
)
returns bit
as
begin

    declare @pos int = 0;
    declare @char varchar(1);
    declare @return bit = 0;

    while @pos < len(@string)
    begin
        select @char = substring(@string, @pos, 1)
        if ascii(@char) < 32 or ascii(@char) > 126 
            begin
                if @checkExtendedCharset = 1
                    begin
                        if ascii(@char) not in (9,124,130,138,142,146,150,154,158,160,170,176,180,181,183,184,185,186,192,193,194,195,196,197,199,200,201,202,203,204,205,206,207,209,210,211,212,213,214,216,217,218,219,220,221,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,248,249,250,251,252,253,254,255)
                            begin
                                select @return = 1;
                                select @pos = (len(@string) + 1)
                            end
                        else
                            begin
                                select @pos = @pos + 1
                            end
                    end
                else
                    begin
                        select @return = 1;
                        select @pos = (len(@string) + 1)    
                    end
            end
        else
            begin
                select @pos = @pos + 1
            end
    end

    return @return;

end

USAGE:

select Address1 
from PropertyFile_English
where udf_ContainsNonASCIIChars(Address1, 1) = 1
2
Deon Robertson