如何使此 IsSimilar(varchar,varchar) 函数性能更高
How to make this IsSimilar(varchar,varchar) function more performant
我需要实现一个函数来查找相似的名字。它用于开发一个新系统,该系统已经迁移了以前系统的数据。
其中一个功能是在创建帐户时,它会尝试查找具有相似名称的人员记录并提出建议。
“John Johnson”的相似名称示例可以是:
- 约翰·约翰逊
- 乔恩·乔森
- 约翰和简约翰逊-彼得斯
- 亲。约翰森
- J。约翰逊
为了实现这一点,我创建了一些 SQL 函数,并且它们在功能上很明智:
[dbo].[Levenshtein]:来自 this question
的评分最高答案的副本
[dbo].[SplitString]:根据'/'、''、'&'、'+'、'-'拆分名称字符串
CREATE FUNCTION [dbo].[SplitString](
@s nvarchar(4000)
)
RETURNS @table TABLE ([value] VARCHAR(4000))
WITH SCHEMABINDING
AS
BEGIN
DECLARE @repl VARCHAR(4000) = REPLACE(REPLACE(REPLACE(REPLACE(@s, '/', '-'),'&', '-'),'+', '-'),'\', '-');
INSERT INTO @table
SELECT TRIM(value) FROM STRING_SPLIT(@repl, '-', NULL)
WHERE RTRIM(value) <> '';
RETURN
END
[dbo].[IsSimilar]:获取 2 个字符串,调用拆分函数并检查拆分的任何部分是否相似,这意味着在 Levenshtein 距离 5 内,初始值或 'Fam'
CREATE FUNCTION [dbo].[IsSimilar](
@s nvarchar(4000)
, @t nvarchar(4000)
)
RETURNS BIT
WITH SCHEMABINDING
AS
BEGIN
DECLARE @sT TABLE (idx int IDENTITY(1,1), [value] VARCHAR(4000))
DECLARE @tT TABLE (idx int IDENTITY(1,1), [value] VARCHAR(4000))
DECLARE @sNrOfWords INT,
@tNrOfWords INT,
@sCount INT = 1,
@tCount INT = 1,
@sVal VARCHAR(4000),
@tVal VARCHAR(4000)
IF (@s = 'fam' OR @s = 'fam.' OR @t = 'fam' OR @t = 'fam.')
return 1
INSERT INTO @sT SELECT [value] FROM [dbo].[SplitString](@s)
INSERT INTO @tT SELECT [value] FROM [dbo].[SplitString](@t)
SET @sNrOfWords = (SELECT COUNT([value]) FROM @sT)
SET @tNrOfWords = (SELECT COUNT([value]) FROM @tT)
IF (@sNrOfWords > 0 AND @tNrOfWords > 0)
BEGIN
WHILE (@sCount <= (SELECT MAX(idx) FROM @sT))
BEGIN
SET @sVal = (SELECT [value] FROM @sT WHERE idx = @sCount)
WHILE (@tCount <= (SELECT MAX(idx) FROM @tT))
BEGIN
SET @tVal = (SELECT [value] FROM @tT WHERE idx = @tCount)
IF (((LEN(@sVal) = 1 OR LEN(@tVal) = 1 OR SUBSTRING(@sVal, 2, 1) IN ('.', ' ') OR SUBSTRING(@tVal, 2, 1) IN ('.', ' ')) AND SUBSTRING(@sVal, 1, 1) = SUBSTRING(@tVal, 1, 1)) OR ((SELECT [dbo].[Levenshtein](@sVal,@tVal, 5)) IS NOT NULL))
RETURN 1
SET @tCount = @tCount + 1
END
SET @sCount = @sCount + 1
SET @tCount = 1
END
END
RETURN 0
END
遗憾的是,这个解决方案并不是最高效的:(
根据我拼错的名字找到这条记录需要 12-13 秒,这当然太长了。
完整的table目前只有512条记录。
对于提高性能有什么帮助吗?
我知道在 SQL 中不推荐循环,所以可能会有一些收获。
我不是 DBA 或 SQL 专家,不知道如何在没有循环的情况下以不同的方式编写它。
不认为我可以使用联接,因为没有相等性。
在执行 OP 评论中的建议后,我设法将相同的 SELECT 语句从 12-13 秒减少到大约 1 秒,这更容易接受。
SplitString 已更改为内联函数:
Create FUNCTION [dbo].[SplitString](
@s nvarchar(4000)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN(
SELECT TRIM(value) AS value FROM STRING_SPLIT(REPLACE(REPLACE(REPLACE(REPLACE(@s, '/', '-'),'&', '-'),'+', '-'),'\', '-'), '-', NULL)
WHERE RTRIM(value) <> ''
);
减少变量并为 IsSimilar 函数使用 Join 语句也有提升:
CREATE FUNCTION [dbo].[IsSimilar](
@s nvarchar(4000)
, @t nvarchar(4000)
)
RETURNS BIT
AS
BEGIN
IF (@s = 'fam' OR @s = 'fam.' OR @t = 'fam' OR @t = 'fam.')
return 1
IF (LEN(TRIM(@s)) > 0 AND LEN(TRIM(@t)) > 0)
BEGIN
RETURN (SELECT IIF (EXISTS(SELECT [sT].[value] FROM (SELECT [value] FROM [dbo].[SplitString](@s)) AS sT INNER JOIN (SELECT [value] FROM [dbo].[SplitString](@t)) AS tT ON (((LEN([sT].[value]) = 1 OR LEN([tT].[value]) = 1 OR SUBSTRING([sT].[value], 2, 1) IN ('.', ' ') OR SUBSTRING([tT].[value], 2, 1) IN ('.', ' ')) AND SUBSTRING([sT].[value], 1, 1) = SUBSTRING([tT].[value], 1, 1)) OR (NOT(SUBSTRING([sT].[value], 2, 1) IN ('.', ' ') OR SUBSTRING([tT].[value], 2, 1) IN ('.', ' ')) AND (SELECT [dbo].[Levenshtein]([sT].[value],[tT].[value], 5)) IS NOT NULL)) ), 1, 0))
END
RETURN 0
END
我不知道这种提升能在多大程度上支持真正的大数据,但在这种情况下情况并非如此,因为每个新帐户创建时个人记录都会链接到帐户记录,而且只有个人记录AccountID = NULL 将被考虑。
我需要实现一个函数来查找相似的名字。它用于开发一个新系统,该系统已经迁移了以前系统的数据。 其中一个功能是在创建帐户时,它会尝试查找具有相似名称的人员记录并提出建议。 “John Johnson”的相似名称示例可以是:
- 约翰·约翰逊
- 乔恩·乔森
- 约翰和简约翰逊-彼得斯
- 亲。约翰森
- J。约翰逊
为了实现这一点,我创建了一些 SQL 函数,并且它们在功能上很明智:
[dbo].[Levenshtein]:来自 this question
的评分最高答案的副本[dbo].[SplitString]:根据'/'、''、'&'、'+'、'-'拆分名称字符串
CREATE FUNCTION [dbo].[SplitString]( @s nvarchar(4000) ) RETURNS @table TABLE ([value] VARCHAR(4000)) WITH SCHEMABINDING AS BEGIN DECLARE @repl VARCHAR(4000) = REPLACE(REPLACE(REPLACE(REPLACE(@s, '/', '-'),'&', '-'),'+', '-'),'\', '-'); INSERT INTO @table SELECT TRIM(value) FROM STRING_SPLIT(@repl, '-', NULL) WHERE RTRIM(value) <> ''; RETURN END
[dbo].[IsSimilar]:获取 2 个字符串,调用拆分函数并检查拆分的任何部分是否相似,这意味着在 Levenshtein 距离 5 内,初始值或 'Fam'
CREATE FUNCTION [dbo].[IsSimilar]( @s nvarchar(4000) , @t nvarchar(4000) ) RETURNS BIT WITH SCHEMABINDING AS BEGIN DECLARE @sT TABLE (idx int IDENTITY(1,1), [value] VARCHAR(4000)) DECLARE @tT TABLE (idx int IDENTITY(1,1), [value] VARCHAR(4000)) DECLARE @sNrOfWords INT, @tNrOfWords INT, @sCount INT = 1, @tCount INT = 1, @sVal VARCHAR(4000), @tVal VARCHAR(4000) IF (@s = 'fam' OR @s = 'fam.' OR @t = 'fam' OR @t = 'fam.') return 1 INSERT INTO @sT SELECT [value] FROM [dbo].[SplitString](@s) INSERT INTO @tT SELECT [value] FROM [dbo].[SplitString](@t) SET @sNrOfWords = (SELECT COUNT([value]) FROM @sT) SET @tNrOfWords = (SELECT COUNT([value]) FROM @tT) IF (@sNrOfWords > 0 AND @tNrOfWords > 0) BEGIN WHILE (@sCount <= (SELECT MAX(idx) FROM @sT)) BEGIN SET @sVal = (SELECT [value] FROM @sT WHERE idx = @sCount) WHILE (@tCount <= (SELECT MAX(idx) FROM @tT)) BEGIN SET @tVal = (SELECT [value] FROM @tT WHERE idx = @tCount) IF (((LEN(@sVal) = 1 OR LEN(@tVal) = 1 OR SUBSTRING(@sVal, 2, 1) IN ('.', ' ') OR SUBSTRING(@tVal, 2, 1) IN ('.', ' ')) AND SUBSTRING(@sVal, 1, 1) = SUBSTRING(@tVal, 1, 1)) OR ((SELECT [dbo].[Levenshtein](@sVal,@tVal, 5)) IS NOT NULL)) RETURN 1 SET @tCount = @tCount + 1 END SET @sCount = @sCount + 1 SET @tCount = 1 END END RETURN 0 END
遗憾的是,这个解决方案并不是最高效的:(
对于提高性能有什么帮助吗? 我知道在 SQL 中不推荐循环,所以可能会有一些收获。 我不是 DBA 或 SQL 专家,不知道如何在没有循环的情况下以不同的方式编写它。 不认为我可以使用联接,因为没有相等性。
在执行 OP 评论中的建议后,我设法将相同的 SELECT 语句从 12-13 秒减少到大约 1 秒,这更容易接受。
SplitString 已更改为内联函数:
Create FUNCTION [dbo].[SplitString](
@s nvarchar(4000)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN(
SELECT TRIM(value) AS value FROM STRING_SPLIT(REPLACE(REPLACE(REPLACE(REPLACE(@s, '/', '-'),'&', '-'),'+', '-'),'\', '-'), '-', NULL)
WHERE RTRIM(value) <> ''
);
减少变量并为 IsSimilar 函数使用 Join 语句也有提升:
CREATE FUNCTION [dbo].[IsSimilar](
@s nvarchar(4000)
, @t nvarchar(4000)
)
RETURNS BIT
AS
BEGIN
IF (@s = 'fam' OR @s = 'fam.' OR @t = 'fam' OR @t = 'fam.')
return 1
IF (LEN(TRIM(@s)) > 0 AND LEN(TRIM(@t)) > 0)
BEGIN
RETURN (SELECT IIF (EXISTS(SELECT [sT].[value] FROM (SELECT [value] FROM [dbo].[SplitString](@s)) AS sT INNER JOIN (SELECT [value] FROM [dbo].[SplitString](@t)) AS tT ON (((LEN([sT].[value]) = 1 OR LEN([tT].[value]) = 1 OR SUBSTRING([sT].[value], 2, 1) IN ('.', ' ') OR SUBSTRING([tT].[value], 2, 1) IN ('.', ' ')) AND SUBSTRING([sT].[value], 1, 1) = SUBSTRING([tT].[value], 1, 1)) OR (NOT(SUBSTRING([sT].[value], 2, 1) IN ('.', ' ') OR SUBSTRING([tT].[value], 2, 1) IN ('.', ' ')) AND (SELECT [dbo].[Levenshtein]([sT].[value],[tT].[value], 5)) IS NOT NULL)) ), 1, 0))
END
RETURN 0
END
我不知道这种提升能在多大程度上支持真正的大数据,但在这种情况下情况并非如此,因为每个新帐户创建时个人记录都会链接到帐户记录,而且只有个人记录AccountID = NULL 将被考虑。