Найти символы, отличные от ASCII, в столбцах varchar с помощью SQL Server
Как можно возвращать строки с символами, отличными от 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)
Ответы
Ответ 1
попробуйте что-то вроде этого:
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)
Ответ 2
Вот решение для поиска по одному столбцу с использованием 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
Ответ 3
Этот script выполняет поиск символов не-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 '|'
Ответ 4
Я успешно использовал этот бит кода
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))
Что хорошо работает для известных столбцов.
Для дополнительного кредита я написал этот быстрый script для поиска всех столбцов 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, но у него есть свои поисковые запросы, подобные этому.
Ответ 5
В Интернете есть функция, определенная пользователем, в "Parse Alphanumeric". Google UDF анализирует буквенно-цифровой код, и вы должны найти для него код. Эта пользовательская функция удаляет все символы, которые не подходят между 0-9, a-z и A-Z.
Select * from Staging.APARMRE1 ar
where udf_parsealpha(ar.last_name) <> ar.last_name
Это должно вернуть любые записи, у которых есть последнее имя с недопустимыми символами для вас... хотя ваш вопрос о бонусных очках является немного более сложной задачей, но я думаю, что аргумент case может справиться с этим. Это немного 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
Я написал это в почтовом ящике форума... поэтому я не совсем уверен, что это будет работать так, как есть, но оно должно быть близко. Я не совсем уверен, как он будет себя вести, если в одной записи есть два поля с недопустимыми символами.
В качестве альтернативы вы должны иметь возможность изменить предложение 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 этого script
Ответ 6
запуск различных решений по некоторым данным реального мира - 12M строк varchar length ~ 30, около 9k изворотливых строк, отсутствие полного текстового индекса в игре, решение patIndex является самым быстрым, а также выбирает большинство строк.
(предварительно пробег км., чтобы установить кеш в известное состояние, запустил 3 процесса и, наконец, снова пробежал километр - последние 2 пробега км дали время в течение 2 секунд)
решение patindex от Gerhard 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 от Deon Robertson - Runtime 3:47, возвращает 7316 строк
select dodgyColumn
from myTable
where dbo.udf_test_ContainsNonASCIIChars(dodgyColumn , 1) = 1
Ответ 7
Вот 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
Ответ 8
Чтобы узнать, какое поле имеет недопустимые символы:
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, уровень 16, состояние 1, строка 3 FOR XML не может сериализовать данные для поля node ', потому что он содержит символ (0x001F), который в XML не допускается. Чтобы получить эти данные с помощью FOR XML, преобразуйте его на двоичный, varbinary или тип данных изображения и использовать BINARY BASE64 директива.
Это очень полезно, когда вы пишете xml файлы и получаете ошибку недопустимых символов при ее проверке.