2010-10-08 1 views
39

Как вернуть строки с символами без символов ASCII с помощью SQL Server?
Если вы можете показать, как это сделать для одного столбца, было бы здорово.Найти символы, отличные от ASCII, в столбцах varchar с использованием 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 | 

недопустимые символы были бы любой вне диапазона ПРОСТРАНСТВА (32) через ~ (127)

ответ

14

попробовать что-то вроде этого:

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

Интересный подход KM. Для моего собственного любопытства ... могу ли я спросить, почему нужна строка «OPTION (MAXRECURSION 1000)» в конце вашего заявления и что он будет делать в этом случае? – Twelfth

+1

«OPTION (MAXRECURSION 1000)» необходим для CTE, который рекурсивно строит набор строк от 1 до 1000, значение по умолчанию равно 100 (я думаю), любые вложенные вызовы рекурсии в cte, превышающие значение по умолчанию, требуют, чтобы эта опция быть установленным. Если у вас есть таблица номеров http://stackoverflow.com/q/1393951/65223, вам не понадобится CTE или эта строка «OPTION (MAXRECURSION 1000)» –

10

Этот сценарий ищет не-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 '|' 
+1

Это работает с одним незначительным изменением. Varchar (128) должен быть больше, потому что хранятся 2 символа. Я сделал это Varchar (200). Для выполнения моей базы данных требуется некоторое время. Я также удивлен тем, что диапазон не может быть использован для упрощения этого процесса. т. е. как «% [^ | - | ~]% 'escape' | ' Я пытался получить диапазон работы, но он не возвращает правильную информацию. –

+0

Я также сменил 127 на 126. Мне не нужен символ DEL. –

2

Там есть определенная пользователем функция доступна на веб-сайте «Анализировать Буквенно» , Google UDF анализирует буквенно-цифровой код, и вы должны найти для него код. Эта пользовательская функция удаляет все символы, которые не подходят между 0-9, a-z и A-Z.

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

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

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 

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

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

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 

Преимущество здесь для каждого столбца вам нужно только распространить заявление профсоюза здесь, в то время как вам нужно положить, что comparisson три раза для каждого столбца в версии сазе этого сценария

+0

Комментарий к себе ... версия case case, я упомянул одну строку с несколькими столбцами с плохими значениями. Если у обоих first_name и last_name было плохое значение в нем ... Я думаю, что оператор case найдет часть first_name и покажет его правильно, но закончит там и не покажет значение last_name правильно. Вероятно, это не оптимальное решение .... версия подзапроса в нижней части моего сообщения, которая объединяет все значения таблиц в id, columnname, формат значений, выглядит гораздо более функциональной и легче следовать. – Twelfth

54

Вот решение для одного поиска колонки с использованием 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 
+0

Это действительно интересно. Не могли бы вы объяснить, как это работает? – StevenWhite

+4

Gerhard обеспечивает регулярное выражение функции PATINDEX. Регулярное выражение [^! - ~]. Я не уверен, почему он включает восклицательный знак там, поскольку он сразу после символа пробела численно. Дело в том, что регулярное выражение находит вещи, которые не являются символами в пространстве Space-Tilde (32-126). – Anssssss

1

Вот 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 

Usage:

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

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

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

решение PATINDEX Герхардом Weiss - Runtime 0:38, возвращает 9144 строк

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

решение подстрочных номеров MT. - Runtime 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 решение по Деон Робертсон - Runtime 3:47, возвращает 7316 строк

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

Я был запущен этот бит кода с успехом

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, но у него есть свои поисковые запросы, подобные этому.

+0

Простой и быстрый. Благодаря! – thrawnis

1

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

SELECT * FROM Staging.APARMRE1 FOR XML AUTO, TYPE 

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

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

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

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

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