Вопрос : Почему функция SOUNDEX не работает с русскими словами?
Ответ: Функция SOUNDEX предназначена для работы только с английским шрифтом и английскими словами.
Это сразу становится понятно, если посмотреть исходный код функции, опубликованный в книге Кена Хендерсона "The Guru's Guide to Transact-SQL". А также становится очевидным тот факт, что транслитерация русских названий в латинские с последующим использованием SOUNDEX не приводит к желаемому результату.
USE master
go
IF OBJECT_ID('sp_soundex') IS NOT NULL
DROP PROC sp_soundex
go
CREATE PROCEDURE sp_soundex @instring varchar(50), @soundex varchar(50)=NULL OUTPUT
/*
Object: sp_soundex Description: Returns the soundex of a string
Usage: sp_soundex @instring=string to translate, @soundex OUTPUT=string in which to return soundex
Returns: (None)
Created by: Ken Henderson. Email: khen@khen.com
Version: 7.0
Example: sp_soundex "Rodgers"
Created: 1998-05-15. Last changed: 1998-05-16.
Notes: Based on the soundex algorithm published by Robert Russell and Margaret ODell in 1918.
Translation to Transact-SQL by Ken Henderson.
*/
AS
IF (@instring='/?') GOTO Help
DECLARE @workstr varchar(10)
SET @instring=UPPER(@instring)
SET @soundex=RIGHT(@instring,LEN(@instring)-1)-- Put all but the first char in a work buffer (we always return the first char)
SET @workstr='AEHIOUWY'-- Remove these from the string
WHILE (@workstr<>'') BEGIN
SET @soundex=REPLACE(@soundex,LEFT(@workstr,1),'')
SET @workstr=RIGHT(@workstr,LEN(@workstr)-1)
END
/*
Translate characters to numbers per the following table:
Char Number
B,F,P,V 1
C,G,J,K,Q,S,X,Z 2
D,T 3
L 4
M,N 5
R 6
*/
SET @workstr='BFPV'
WHILE (@workstr<>'') BEGIN
SET @soundex=REPLACE(@soundex,LEFT(@workstr,1),'1')
SET @workstr=RIGHT(@workstr,LEN(@workstr)-1)
END
SET @workstr='CGJKQSXZ'
WHILE (@workstr<>'') BEGIN
SET @soundex=REPLACE(@soundex,LEFT(@workstr,1),'2')
SET @workstr=RIGHT(@workstr,LEN(@workstr)-1)
END
SET @workstr='DT'
WHILE (@workstr<>'') BEGIN
SET @soundex=REPLACE(@soundex,LEFT(@workstr,1),'3')
SET @workstr=RIGHT(@workstr,LEN(@workstr)-1)
END
SET @soundex=REPLACE(@soundex,'L','4')
SET @workstr='MN'
WHILE (@workstr<>'') BEGIN
SET @soundex=REPLACE(@soundex,LEFT(@workstr,1),'5')
SET @workstr=RIGHT(@workstr,LEN(@workstr)-1)
END
SET @soundex=REPLACE(@soundex,'R','6')
-- Now replace repeating digits (e.g., '11' or '22') with single digits
DECLARE @c int
SET @c=1
WHILE (@c<10) BEGIN
SET @soundex=REPLACE(@soundex,CONVERT(char(2),@c*11),CONVERT(char(1),@c))-- Multiply by 11 to produce repeating digits
SET @c=@c+1
END
SET @soundex=REPLACE(@soundex,'00','0')-- Get rid of double zeros
SET @soundex=LEFT(@soundex,3)
WHILE (LEN(@soundex)<3) SET @soundex=@soundex+'0'-- Pad with zero
SET @soundex=LEFT(@instring,1)+@soundex-- Prefix first char and return
RETURN 0
Help:
EXEC sp_usage @objectname='sp_soundex',
@desc='Returns the soundex of a string',
@parameters='@instring=string to translate, @soundex OUTPUT=string in which to return soundex',
@author='Ken Henderson',
@email='khen@khen.com',
@datecreated='19980515',
@datelastchanged='19980516',
@version='7',
@revision='0',
@example='sp_soundex "Rodgers"'
RETURN -1
GO
Проверить соответствие этой процедуры и функции SOUNDEX можно с помощью следующего кода:
DECLARE @mysx varchar(4)
EXEC sp_soundex 'Rogers',@mysx OUTPUT
SELECT @mysx,SOUNDEX('Rogers')
GO
Результат:
---- -----
R262 R262
(1 row(s) affected)
Для английского языка кроме алгоритма SoundEx существует алгоритм MetaPhone. Реализация этого алгоритма для русского языка показал Каньковски Пётр (kankowski@narod.ru) в журнале "Программист" за август 2002 года. Вы можете воспользоваться функцией MetaPhoneRu, переведенной мной на Transact-SQL:
IF exists (SELECT * from dbo.sysobjects where id = object_id(N'[dbo].[MetaPhoneRu]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[MetaPhoneRu]
GO
CREATE FUNCTION dbo.MetaPhoneRu (@W varchar(4000))
RETURNS varchar(4000)
AS
BEGIN
DECLARE @alf varchar(4000), @cns1 varchar(4000), @cns2 varchar(4000), @cns3 varchar(4000), @ch varchar(4000), @ct varchar(4000)
SET @alf = 'ОЕАИУЭЮЯПСТРКЛМНБВГДЖЗЙФХЦЧШЩЁЫ'
SET @cns1 = 'БЗДВГ'
SET @cns2 = 'ПСТФК'
SET @cns3 = 'ПСТКБВГДЖЗФХЦЧШЩ'
SET @ch = 'ОЮЕЭЯЁЫ'
SET @ct = 'АУИИАИА'
-- @alf - алфавит кроме исключаемых букв, @cns1 и @cns2 - звонкие и глухие
-- согласные, @cns3 - согласные, перед которыми звонкие оглушаются,
-- @ch, @ct - образец и замена гласных
SET @i = 1
WHILE @i <= LEN(@W)
BEGIN
SET @c = SUBSTRING(@W, @i, 1)
IF CHARINDEX(@c, @alf)>0 SET @S = @S + @c
SET @i=@i+1
END
IF LEN(@S) = 0 RETURN ''
-- Заменяем окончания
IF LEN(@S)>6
SET @S = LEFT(@S, LEN(@S) - 6) +
CASE RIGHT(@S, 6)
WHEN 'ОВСКИЙ' THEN '@'
WHEN 'ЕВСКИЙ' THEN '#'
WHEN 'ОВСКАЯ' THEN '$'
WHEN 'ЕВСКАЯ' THEN '%'
ELSE RIGHT(@S, 6)
END
IF LEN(@S)>4
SET @S = LEFT(@S, LEN(@S) - 4) +
CASE RIGHT(@S, 4)
WHEN 'ИЕВА' THEN '9'
WHEN 'ЕЕВА' THEN '9'
ELSE RIGHT(@S, 4)
END
IF LEN(@S)>3
SET @S = LEFT(@S, LEN(@S) - 3) +
CASE RIGHT(@S, 3)
WHEN 'ОВА' THEN '9'
WHEN 'ЕВА' THEN '9'
WHEN 'ИНА' THEN '1'
WHEN 'ИЕВ' THEN '4'
WHEN 'ЕЕВ' THEN '4'
WHEN 'НКО' THEN '3'
ELSE RIGHT(@S, 3)
END
IF LEN(@S)>2
SET @S = LEFT(@S, LEN(@S) - 2) +
CASE RIGHT(@S, 2)
WHEN 'ОВ' THEN '4'
WHEN 'ЕВ' THEN '4'
WHEN 'АЯ' THEN '6'
WHEN 'ИЙ' THEN '7'
WHEN 'ЫЙ' THEN '7'
WHEN 'ЫХ' THEN '5'
WHEN 'ИХ' THEN '5'
WHEN 'ИН' THEN '8'
WHEN 'ИК' THEN '2'
WHEN 'ЕК' THEN '2'
WHEN 'УК' THEN '0'
WHEN 'ЮК' THEN '0'
ELSE RIGHT(@S, 2)
END
-- Оглушаем последний символ, если он - звонкий согласный:
SET @B = CHARINDEX(RIGHT(@S, 1), @cns1)
IF @B > 0
SET @S = LEFT(@S, LEN(@S)-1) + SUBSTRING(@cns2, @B, 1)
SET @old_c = ' '
SET @i = 1
WHILE @i <= LEN(@S)
BEGIN
SET @c = SUBSTRING(@S, @i, 1)
SET @B = CHARINDEX(@c, @ch)
IF @B > 0
BEGIN
IF @old_c = 'Й' OR @old_c = 'И'
BEGIN
IF @c = 'О' OR @c = 'Е'
BEGIN
SET @old_c = 'И'
SET @S = LEFT(@S, LEN(@S)-1) + @old_c
END
ELSE
IF @c <> @old_c SET @V = @V + SUBSTRING(@ct, @B, 1)
END
ELSE
BEGIN
IF @c <> @old_c SET @V = @V + SUBSTRING(@ct, @B, 1)
END
END
ELSE
BEGIN
IF @c <> @old_c
AND CHARINDEX(@c, @cns3)>0
BEGIN
SET @B = CHARINDEX(@old_c, @cns1)
IF @B>0
BEGIN
SET @old_c = SUBSTRING(@cns2, @B, 1)
SET @V = LEFT(@V, LEN(@V)-1) + @old_c
END
END
IF @c <> @old_c SET @V = @V + @c
END
SET @old_c = @c
SET @i = @i + 1
END
RETURN (@V)
END
GO
А вот и пример работы с этой функцией:
SELECT dbo.MetaPhoneRu('Грицюк')
UNION ALL
SELECT dbo.MetaPhoneRu('Грицук')
UNION ALL
SELECT dbo.MetaPhoneRu('Грецук')
UNION ALL
SELECT dbo.MetaPhoneRu('Аввакумов')
UNION ALL
SELECT dbo.MetaPhoneRu('Авакумов')
UNION ALL
SELECT dbo.MetaPhoneRu('Авакуумов')
Совсем другой "коленкор". Только для сравнения строк, содержащих несколько слов (например, ФИО) процедуру необходимо доработать.
Еще для сравнения слов существует функция, авторство которой теряется в дебрях интернета в процессе переделки с одного языка программирования на другой. Принцип работы этой функции больше похож на принцип работы функции DIFFERENCE, но в отличие от нее выдает меньший коэффициент при меньшем расхождении слов:
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[StrCompareEx]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[StrCompareEx]
GO
CREATE FUNCTION dbo.StrCompareEx (@p varchar(255), @t varchar(255), @maxdist tinyint)
RETURNS integer AS
BEGIN
DECLARE @dist tinyint
DECLARE @d varchar(255)
DECLARE @dp varchar(255)
DECLARE @dt varchar(255)
DECLARE @plen tinyint
DECLARE @tlen tinyint
SELECT @plen = LEN(@p)
SELECT @tlen = LEN(@t)
DECLARE @i tinyint
DECLARE @j tinyint
SELECT @i = 1
SELECT @d = ''
WHILE @i<=@plen
BEGIN
SELECT @d = @d + CHAR(@i)
SELECT @i = @i + 1
END
SELECT @i = @i + 1
END
SELECT @ctp = @ct
SELECT @dp = @d
SELECT @d = @dt
SELECT @j = @j + 1
END
SELECT @dist = ASCII(substring(@d,@plen,1))
IF @dist > @maxdist + 1
SELECT @dist = @maxdist + 1
RETURN @dist
END
GO
Рассмотрим пример работы с этой функцией, а также результат ее работы совместно с предыдущей функцией MetaPhone:
CREATE TABLE #Table1 (Name varchar(200))
INSERT #Table1
SELECT 'Иевлев'
UNION ALL
SELECT 'Иванова'
UNION ALL
SELECT 'Петров'
UNION ALL
SELECT 'Сидоров'
UNION ALL
SELECT 'Илонов'
UNION ALL
SELECT 'Тифонов'
UNION ALL
SELECT 'Иванко'
UNION ALL
SELECT 'Данко'
SELECT Name,
dbo.StrCompareEx(Name, 'Иванов', 20) AS Dist,
dbo.StrCompareEx(dbo.MetaPhoneRu(Name), dbo.MetaPhoneRu('Иванов'), 20) AS DistEx
FROM #Table1
Подведем итог.
Использование стандартной функции SOUNDEX возможно только с английскими словами. Для работы с русскими словами можно использовать приведенные выше функции или найти более совершенные алгоритмы сравнения.
Надеюсь, Вы нашли эту информацию полезной. Вопросы, предложения и пожелания шлите на адрес sql@likor.ru С уважением, Сергей Кошкин.
Архив рассылки смотрите на сайте Рассылки http://sql.softmatics.ru/