2010-10-08 37 views
39

¿Cómo se pueden devolver las filas con caracteres que no sean ASCII utilizando SQL Server?
Si puede mostrar cómo hacerlo para una columna sería genial.Buscar caracteres que no sean ASCII en columnas varchar con SQL Server

que estoy haciendo algo como esto ahora, pero no está funcionando

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

Para el crédito adicional, si puede abarcar todos los varchar columnas en una tabla, que sería excepcional! En esta solución, sería bueno devolver tres columnas:

  • El campo de identidad para ese registro. (Esto permitirá que todo el disco a ser revisada con otra consulta.)
  • El nombre de la columna
  • El texto con el carácter no válido
Id | FieldName | InvalidText  | 
----+-----------+-------------------+ 
25 | LastName | Solís    | 
56 | FirstName | François   | 
100 | Address1 | 123 Ümlaut street | 

caracteres no válidos serían cualquiera fuera del intervalo de espacio (32) a través de ~ (127)

Respuesta

14

intentar algo como esto:

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) 

SALIDA:

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) 
+0

enfoque interesante KM. Por mi propia curiosidad ... ¿puedo preguntar por qué la línea "OPCIÓN (MAXRECURSIÓN 1000)" al final de su declaración es necesaria y qué hará en este caso? – Twelfth

+1

"OPTION (MAXRECURSION 1000)" es necesario que el CTE, que forma recursiva construye un conjunto de filas de 1 a 1000, el valor por defecto es 100 (creo) llamadas de recursividad anidadas en un CTE exceder el valor por defecto requiere esta opción para ser establecido. Si usted tenía una tabla de números http://stackoverflow.com/q/1393951/65223 usted no necesitaría el CTE o esta "opción (MAXRECURSION 1000)" línea –

10

Este script busca para caracteres no ASCII en una columna. Se genera una cadena de todos los caracteres válidos, aquí punto de código de 32 a 127. A continuación, se busca filas que no coinciden con la lista:

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 '|' 
+1

Esto funciona con un cambio menor VARCHAR (128) tiene que ser más grande porque 2 caracteres están siendo almacenados. Lo hice Varchar (200). Lleva algo de tiempo ejecutar mi base de datos. También me sorprende que un rango no pueda usarse para simplificar este proceso. es decir, como '% [^ | - | ~]% 'escape' | ' Intenté hacer funcionar un rango pero no devuelve la información correcta. –

+0

También cambié 127 a 126. No quería el carácter DEL. –

2

Hay una función definida por el usuario disponible en la web 'Analizar alfanumérico' . Google UDF analizar alfanumérico y debe encontrar el código para ello. Esta función definida por el usuario elimina todos los caracteres que no se ajustan entre 0-9, a-z y A-Z.

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

que debe traer de vuelta a todos los registros que tienen una apellidos con caracteres no válidos para usted ... aunque su pregunta puntos de bonificación es un poco más que un reto, pero creo que una declaración de caso podría manejarlo. Este es un código poco psíquico, no estoy del todo seguro de si funcionaría.

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 

escribí esto en el cuadro de mensaje en el foro ... así que no estoy muy seguro de si que va a funcionar como es, pero debe estar cerca. No estoy seguro de cómo se comportará si un solo registro tiene dos campos con caracteres no válidos tampoco.

Como alternativa, usted debe ser capaz de cambiar la cláusula from lejos de una sola mesa y en una subconsulta que se ve algo como:

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 

ventaja aquí es que cada columna lo único que necesita para extender la declaración de la Unión aquí, mientras que necesita para poner ese comparisson tres veces para cada columna en la versión declaración de caso de este script

+0

Comente sobre mí mismo ... la versión de la declaración del caso, mencioné una sola fila con múltiples columnas con valores incorrectos. Si tanto first_name como last_name tienen un valor incorrecto ... creo que la declaración case encontrará la porción first_name y la mostrará correctamente, pero terminará allí y no mostrará correctamente el last_name value. Probablemente no es una solución óptima .... la versión sub consulta en la parte inferior de mi puesto que los sindicatos todas las tablas de valores en ID, nombrecolumna, formato de valores parece ser mucho más funcional y más fácil de seguir – Twelfth

54

Aquí es una solución para la búsqueda de una sola columna usando PATINDEX.
También muestra los caracteres StartPosition, InvalidCharacter y 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 
+0

Esto es realmente interesante. ¿Podrías explicarme cómo funciona esto? – StevenWhite

+4

Gerhard proporciona una expresión regular a la función PATINDEX. La expresión regular es [^! - ~]. No estoy seguro de por qué incluye el carácter de exclamación, ya que está justo después del carácter de espacio numéricamente. El punto es que la expresión regular encuentra cosas que son caracteres que no están en el rango de Space-Tilde (32-126). – Anssssss

1

Aquí hay una UDF que construí para detectar las columnas con ascii charaters extendidos. Es rápido y puede ampliar el conjunto de caracteres que desea verificar. El segundo parámetro le permite cambiar entre comprobar nada fuera del juego de caracteres estándar o permitir que un conjunto extendido:

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 

USO:

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

ejecutando distintas soluciones en un mundo real de los datos - filas 12M varchar longitud ~ 30, alrededor de 9k filas dudosas, sin índice de texto completo en juego, la solución patIndex es la más rápida, y también selecciona la mayoría de las filas.

(. Pre-RAN km para establecer el caché a un estado conocido, corrió los 3 procesos, y finalmente corrió km vez más - las 2 últimas carreras de km dieron veces en 2 segundos) solución patindex

por Gerhard Weiss - Runtime 0:38, devuelve 9144 filas

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

la solución substring-numbers de MT. - Duración 1:16, volvió 8996 filas

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 

solución UDF por Deon Robertson - Tiempo de ejecución 03:47, devuelve 7316 filas

select dodgyColumn 
from myTable 
where dbo.udf_test_ContainsNonASCIIChars(dodgyColumn , 1) = 1 
7

He estado corriendo este fragmento de código con éxito

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)) 

Que funciona bien para columnas conocidas.

Para obtener crédito adicional, escribí este guión rápido para buscar todas las columnas nvarchar en una tabla dada para caracteres 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) 

No soy seguidor de SQL dinámico pero tiene sus usos para consultas exploratorias como esta.

+0

Simple y rápido. ¡Gracias! – thrawnis

1

Para saber qué campo tiene caracteres no válidos:

SELECT * FROM Staging.APARMRE1 FOR XML AUTO, TYPE 

puede probarlo con esta consulta:

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

el resultado será:

Msg 6841, nivel 16 , Estado 1, línea 3 FOR XML no pudo serializar los datos para el nodo 'campo' porque contiene un carácter (0x001F) que no está permitido en XML. Para recuperar estos datos utilizando FOR XML, conviértalo a un tipo de datos binario, varbinary o de imagen y utilice la directiva BINARY BASE64 .

Es muy útil cuando escribe archivos xml y obtiene un error de caracteres no válidos cuando lo valida.

Cuestiones relacionadas