Trouver des caractères non-ASCII dans les colonnes varchar en utilisant SQL Server
-
28-09-2019 - |
Question
Comment les lignes avec des caractères non-ASCII sont retournés en utilisant SQL Server?
Si vous pouvez montrer comment le faire pour une colonne serait grande.
Je suis en train de faire quelque chose comme ça maintenant, mais il ne fonctionne pas
select *
from Staging.APARMRE1 as ar
where ar.Line like '%[^!-~ ]%'
Pour un crédit supplémentaire, si elle peut couvrir tous colonnes de varchar
dans une table, ce serait exceptionnel! Dans cette solution, ce serait bien de revenir trois colonnes:
- Le champ d'identité pour cet enregistrement. (Cela permettra à l'ensemble du dossier à examiner avec une autre requête.)
- Le nom de colonne
- Le texte avec le caractère non valide
Id | FieldName | InvalidText |
----+-----------+-------------------+
25 | LastName | Solís |
56 | FirstName | François |
100 | Address1 | 123 Ümlaut street |
caractères incorrects serait tout en dehors de la gamme de l'espace (32 10 ) à travers ~
(127 10 )
La solution
essayer quelque chose comme ceci:
DECLARE @YourTable table (PK int, col1 varchar(20), col2 varchar(20), col3 varchar(20))
INSERT @YourTable VALUES (1, 'ok','ok','ok')
INSERT @YourTable VALUES (2, 'BA'+char(182)+'D','ok','ok')
INSERT @YourTable VALUES (3, 'ok',char(182)+'BAD','ok')
INSERT @YourTable VALUES (4, 'ok','ok','B'+char(182)+'AD')
INSERT @YourTable VALUES (5, char(182)+'BAD','ok',char(182)+'BAD')
INSERT @YourTable VALUES (6, 'BAD'+char(182),'B'+char(182)+'AD','BAD'+char(182)+char(182)+char(182))
--if you have a Numbers table use that, other wise make one using a CTE
;WITH AllNumbers AS
( SELECT 1 AS Number
UNION ALL
SELECT Number+1
FROM AllNumbers
WHERE Number<1000
)
SELECT
pk, 'Col1' BadValueColumn, CONVERT(varchar(20),col1) AS BadValue --make the XYZ in convert(varchar(XYZ), ...) the largest value of col1, col2, col3
FROM @YourTable y
INNER JOIN AllNumbers n ON n.Number <= LEN(y.col1)
WHERE ASCII(SUBSTRING(y.col1, n.Number, 1))<32 OR ASCII(SUBSTRING(y.col1, n.Number, 1))>127
UNION
SELECT
pk, 'Col2' BadValueColumn, CONVERT(varchar(20),col2) AS BadValue --make the XYZ in convert(varchar(XYZ), ...) the largest value of col1, col2, col3
FROM @YourTable y
INNER JOIN AllNumbers n ON n.Number <= LEN(y.col2)
WHERE ASCII(SUBSTRING(y.col2, n.Number, 1))<32 OR ASCII(SUBSTRING(y.col2, n.Number, 1))>127
UNION
SELECT
pk, 'Col3' BadValueColumn, CONVERT(varchar(20),col3) AS BadValue --make the XYZ in convert(varchar(XYZ), ...) the largest value of col1, col2, col3
FROM @YourTable y
INNER JOIN AllNumbers n ON n.Number <= LEN(y.col3)
WHERE ASCII(SUBSTRING(y.col3, n.Number, 1))<32 OR ASCII(SUBSTRING(y.col3, n.Number, 1))>127
order by 1
OPTION (MAXRECURSION 1000)
SORTIE:
pk BadValueColumn BadValue
----------- -------------- --------------------
2 Col1 BA¶D
3 Col2 ¶BAD
4 Col3 B¶AD
5 Col1 ¶BAD
5 Col3 ¶BAD
6 Col1 BAD¶
6 Col2 B¶AD
6 Col3 BAD¶¶¶
(8 row(s) affected)
Autres conseils
Voici une solution pour la recherche de la colonne unique à l'aide PATINDEX.
Il affiche également le StartPosition, InvalidCharacter et code ASCII.
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
Ce script recherche pour les caractères non-ascii dans une colonne. Il génère une chaîne de tous les caractères valides, ici point 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 '|'
Je dirige ce petit 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, je l'ai écrit ce script rapide pour rechercher toutes les colonnes nvarchar dans une table 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 fan de SQL dynamique, mais il a ses utilisations pour les requêtes exploratoires comme celui-ci.
l'exécution des différentes solutions sur des données réelles - lignes 12M longueur varchar ~ 30, environ 9k lignes louches, sans index en texte intégral en jeu, la solution patindex est le plus rapide, et il sélectionne également les lignes les plus.
(. Pré-ran km pour définir le cache à un état connu, a couru les 3 processus, et enfin a couru à nouveau km - les 2 dernières séries de km ont donné temps à moins de 2 secondes)
solution de patindex par Gerhard Weiss - Durée 00:38, 9144 renvoie lignes
select dodgyColumn from myTable fcc
WHERE patindex('%[^ !-~]%' COLLATE Latin1_General_BIN,dodgyColumn ) >0
la solution sous-chaîne numéros par MT. - Runtime 01h16, retour 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
udf solution par Deon Robertson - Durée 03:47, 7316 renvoie lignes
select dodgyColumn
from myTable
where dbo.udf_test_ContainsNonASCIIChars(dodgyColumn , 1) = 1
Il y a une fonction définie par l'utilisateur disponible sur le web « Parse Alphanumeric ». Google UDF parse alphanumérique et vous devez trouver le code pour elle. Cette fonction définie par l'utilisateur supprime tous les caractères qui ne correspondent 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 Ramenez tous les dossiers qui ont un last_name avec les caractères non valides pour vous ... si vos points bonus question est un peu plus difficile, mais je pense une déclaration de cas pourrait le manipuler. Ceci est un pseudo-code binaire, je ne suis pas tout à fait sûr si elle travaillerait.
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 dans la rubrique post-forum ... donc je ne suis pas tout à fait sûr si cela va fonctionner comme est, mais il devrait être proche. Je ne suis pas sûr de savoir comment il se comportera si un enregistrement unique a deux champs avec les caractères non valides soit.
Comme alternative, vous devriez être en mesure de modifier la clause de loin d'une seule table et dans 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
avantage ici est pour chaque colonne que vous aurez seulement besoin d'étendre la déclaration du syndicat ici, alors que vous devez mettre cette comparisson trois fois pour chaque colonne dans la version de l'instruction cas de ce script
Voici une UDF I construit aux colonnes detectc avec charaters ascii étendues. Il est rapide et vous pouvez étendre le jeu de caractères que vous voulez vérifier. Le second paramètre vous permet de basculer entre les vérifier quoi que ce soit en dehors du jeu de caractères standard ou permettant un ensemble é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
Utilisation:
select Address1
from PropertyFile_English
where udf_ContainsNonASCIIChars(Address1, 1) = 1
Pour quel champ contient des caractères incorrects:
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 FOR XML ne pouvait pas sérialiser la données pour le nœud « champ », car il contient un caractère (0x001F) qui ne sont pas autorisés en XML. Pour récupérer ces données en utilisant FOR XML, convertir en binaire, varbinary ou de l'image type de données et utiliser le BINARY BASE64 directive.
Il est très utile lorsque vous écrivez des fichiers xml et obtenez erreur de caractères non valides lors de la valider.