Найти не-ASCII символы в столбцах varchar с помощью SQL Server - PullRequest
50 голосов
/ 08 октября 2010

Как можно вернуть строки с не-ASCII-символами, используя SQL Server?
Если бы вы могли показать, как это сделать для одного столбца, было бы здорово.

Я делаю что-то подобное сейчас, но это не работает

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

Для дополнительного кредита, , если оно может охватывать все varchar столбцов в таблице, это было бы замечательно!В этом решении было бы неплохо вернуть три столбца:

  • Поле идентификатора для этой записи.(Это позволит просматривать всю запись с помощью другого запроса.)
  • Имя столбца
  • Текст с недопустимым символом
 Id | FieldName | InvalidText       |
----+-----------+-------------------+
 25 | LastName  | Solís             |
 56 | FirstName | François          |
100 | Address1  | 123 Ümlaut street |

Недопустимые символыбудет вне диапазона SPACE (от 32 10 ) до ~ (127 10 )

Ответы [ 8 ]

69 голосов
/ 12 октября 2010

Вот решение для поиска по одному столбцу с использованием PATINDEX.
Он также отображает код StartPosition, InvalidCharacter и 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
20 голосов
/ 08 октября 2010

попробуйте что-то вроде этого:

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)

ВЫВОД:

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)
13 голосов
/ 08 октября 2010

Этот скрипт ищет не-ascii символы в одном столбце. Он генерирует строку из всех допустимых символов, здесь код с номерами от 32 до 127. Затем он ищет строки, которые не соответствуют списку:

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 '|'
12 голосов
/ 11 января 2016

Я успешно выполняю этот фрагмент кода

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

, который хорошо работает для известных столбцов.

Для дополнительной поддержки я написал этот быстрый скрипт для поиска во всех столбцах nvarcharв данной таблице для символов 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)

Я не фанат динамического SQL, но у него есть свои применения для поисковых запросов, подобных этому.

6 голосов
/ 10 марта 2015

запуск различных решений на некоторых реальных данных - длина 12 млн строк varchar ~ 30, около 9 тыс. Хитрых строк, отсутствие полнотекстового индекса в игре, решение patIndex является самым быстрым, и оно также выбирает большинство строк.

(предварительно пробегал км., Чтобы установить кэш в известное состояние, запускал 3 процесса и, наконец, снова пробегал км - последние 2 прогона км давали время в течение 2 секунд)

Решение patindex от Gerhard Weiss - время выполнения 0:38, возвращает 9144 строки

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

решение для подстрок чисел по MT.- Время выполнения 1:16, возвращено 8996 строк

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-решение от Deon Robertson - Время выполнения 3:47, возвращается 7316 строк

select dodgyColumn 
from myTable 
where dbo.udf_test_ContainsNonASCIIChars(dodgyColumn , 1) = 1
3 голосов
/ 08 октября 2010

Существует определенная пользователем функция, доступная в сети «Parse Alphanumeric».Google UDF разбирает буквенно-цифровые символы, и вы должны найти код для этого.Эта пользовательская функция удаляет все символы, которые не помещаются между 0-9, az и AZ.

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

Это должно вернуть любые записи, которые имеют для вас last_name с недопустимыми символами ... хотяВаш вопрос о бонусных баллах - это немного сложнее, но я думаю, что с заявлением о ситуации можно справиться.Это немного псевдо-код, я не совсем уверен, будет ли он работать.

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 

Я написал это в почтовом ящике форума ... так что я не совсем уверен, если это будетФункция как есть, но она должна быть близка.Я не совсем уверен, как он будет себя вести, если в одной записи есть два поля с недопустимыми символами.

В качестве альтернативы вы должны иметь возможность изменить предложение from из одной таблицы в подзапрос.это выглядит примерно так:

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

Преимущество здесь в том, что для каждого столбца вам нужно только расширять оператор объединения, в то время как вам нужно поместить это сравнение три раза для каждого столбца в версии оператора caseэтот сценарий

2 голосов
/ 25 февраля 2016

Чтобы найти, какое поле содержит недопустимые символы:

SELECT * FROM Staging.APARMRE1 FOR XML AUTO, TYPE

Вы можете проверить его с помощью этого запроса:

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

Результат будет:

Сообщение 6841, уровень 16, состояние 1, строка 3 FOR XML не удалось сериализовать данные для узла «поле», поскольку оно содержит символ (0x001F), который не разрешен в XML.Чтобы получить эти данные с помощью FOR XML, преобразуйте их в двоичный, varbinary или тип данных изображения и используйте директиву BINARY BASE64.

Это очень полезно, когда вы пишете XML-файлы и получаете ошибку недопустимых символов, когдаподтвердите это.

2 голосов
/ 22 января 2015

Вот UDF, который я построил для обнаружения столбцов с расширенными символами ascii. Это быстро, и вы можете расширить набор символов, который вы хотите проверить. Второй параметр позволяет вам переключаться между проверкой чего-либо вне стандартного набора символов или разрешением расширенного набора:

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

ИСПОЛЬЗОВАНИЕ:

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