SQL中两个字符串的公共子串

时间:2018-03-20 21:07:33

标签: sql-server string tsql

我需要在SQL中找到两个字符串的公共子字符串(没有空格)。

查询:

select *
from tbl as a, tbl as b
where a.str <> b.str

示例数据:

str1      | str2      | max substring without spaces
----------+-----------+-----------------------------
aabcdfbas | rikcdfva  | cdf
aaab akuc | aaabir a  | aaab
ab akuc   | ab atr    | ab

1 个答案:

答案 0 :(得分:0)

我不同意那些说SQL不是这项工作的工具的人。除非有人能以比我在任何编程语言中的解决方案更快的方式向我展示,否则我会认为SQL(基于集合,无副作用,仅使用不可变变量)是 ONLY 此工作的工具(处理varchar(8000) - 或nvarchar(4000)时)。以下解决方案适用于varchar(8000)。

<强> 1。一个正确索引的标签(数字)表。

-- (1) build and populate a persisted (numbers) tally 
IF OBJECT_ID('dbo.tally') IS NOT NULL DROP TABLE dbo.tally;
CREATE TABLE dbo.tally (n int not null);

WITH DummyRows(V) AS(SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t(N))
INSERT dbo.tally
SELECT TOP (8000) ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM DummyRows a CROSS JOIN DummyRows b CROSS JOIN DummyRows c CROSS JOIN DummyRows d;

-- (2) Add Required constraints (and indexes) for performance
ALTER TABLE dbo.tally 
ADD CONSTRAINT pk_tally PRIMARY KEY CLUSTERED(N) WITH FILLFACTOR = 100;

ALTER TABLE dbo.tally 
ADD CONSTRAINT uq_tally UNIQUE NONCLUSTERED(N);

请注意,计数表功能也不会执行。

<强> 2。使用我们的计数表返回所有可能的子字符串

使用&#34; abcd&#34;例如,让我们获取它的所有子串。请注意我的评论。

DECLARE @s1 varchar(8000) = 'abcd';

SELECT
  position  = t.N,
  tokenSize = x.N,
  string    = substring(@s1, t.N, x.N)  
FROM       dbo.tally t -- token position
CROSS JOIN dbo.tally x -- token length
WHERE t.N <=  len(@s1) -- all positions
AND   x.N <=  len(@s1) -- all lengths
AND   len(@s1) - t.N - (x.N-1) >= 0 -- filter unessesary rows [e.g.substring('abcd',3,2)]

返回

position    tokenSize   string
----------- ----------- -------
1           1           a
2           1           b
3           1           c
4           1           d
1           2           ab
2           2           bc
3           2           cd
1           3           abc
2           3           bcd
1           4           abcd

第3。 dbo.getshortstring8K

这个功能是什么?第一个主要优化。我们要将两个字符串中较短的字符串分解为每个可能的子字符串,然后查看它是否存在于较长的字符串中。如果你有两个字符串(S1和S2)并且S1长于S2,我们知道S1的子字符串中没有一个长于S2的子字符串将是S2的子字符串。这就是dbo.getshortstring的目的:确保我们不执行任何不必要的子串比较。这会在一瞬间变得更有意义。

这非常重要,因为字符串中的子串数可以使用Triangle Number Function来计算。当 N 作为字符串中的长度(字符数)时,子串的数量可以计算为 N *(N + 1)/ 2 。例如。 &#34; ABC&#34;有6个子串:3 *(3 + 1)/ 2 = 6; A,B,C,AB,BC,ABC。如果我们要比较&#34; abc&#34;到&#34; abcdefgh&#34;我们不需要检查是否&#34; abcd&#34;是&#34; abc&#34;。

的子字符串

打破&#34; abcdefgh&#34; (长度= 8)到所有可能的子串中需要8 *(8 + 1)/ 2 = 36 操作(vs 6 用于&#34; abc&#34;)。

IF OBJECT_ID('dbo.getshortstring8k') IS NOT NULL DROP FUNCTION dbo.getshortstring8k;
GO
CREATE FUNCTION dbo.getshortstring8k(@s1 varchar(8000), @s2 varchar(8000))
RETURNS TABLE WITH SCHEMABINDING AS RETURN 
SELECT s1 = CASE WHEN LEN(@s1) < LEN(@s2) THEN @s1 ELSE @s2 END,
       s2 = CASE WHEN LEN(@s1) < LEN(@s2) THEN @s2 ELSE @s1 END;

<强> 4。查找较长字符串中存在的较短字符串的所有子环:

DECLARE @s1 varchar(8000) = 'bcdabc', @s2 varchar(8000) = 'abcd';

SELECT
  s.s1, -- test to make sure s.s1 is the shorter of the two strings
  position  = t.N,
  tokenSize = x.N,
  string    = substring(s.s1, t.N, x.N)
FROM dbo.getshortstring8k(@s1, @s2) s --<< get the shorter string
CROSS JOIN dbo.tally t  
CROSS JOIN dbo.tally x
WHERE t.N between 1 and len(s.s1)
AND   x.N between 1 and len(s.s1)
AND   len(s.s1) - t.N - (x.N-1) >= 0
AND   charindex(substring(s.s1, t.N, x.N), s.s2) > 0;

<强> 5。仅检索最长的公共子串

这很容易。我们只需将TOP (1) WITH TIES添加到我们的SELECT语句中,我们即可完成所有设置。这里,最长的公共子串是&#34; bc&#34;和&#34; xx&#34;

DECLARE @s1 varchar(8000) = 'xxabcxx', @s2 varchar(8000) = 'bcdxx';

SELECT TOP (1) WITH TIES 
  position  = t.N,
  tokenSize = x.N,
  string    = substring(s.s1, t.N, x.N)
FROM dbo.getshortstring8k(@s1, @s2) s
CROSS JOIN dbo.tally t  
CROSS JOIN dbo.tally x
WHERE t.N between 1 and len(s.s1)
AND   x.N between 1 and len(s.s1)
AND   len(s.s1) - t.N - (x.N-1) >= 0
AND   charindex(substring(s.s1, t.N, x.N), s.s2) > 0
ORDER BY x.N DESC;

<强> 6。将此逻辑应用于您的表格

使用APPLY,我们将变量@ s1和@ s2替换为t.str1&amp; t.str2。我添加了一个过滤器来排除包含空格的匹配项(请参阅我的评论)......我们已经关闭了:

-- easily consumbable sample data
DECLARE @yourtable TABLE (str1 varchar(8000), str2 varchar(8000));
INSERT @yourtable 
VALUES ('aabcdfbas','rikcdfva'),('aaab akuc','aaabir a'),('ab akuc','ab atr');

SELECT str1, str2,  [max substring without spaces] = string
FROM @yourtable t
CROSS APPLY 
(
    SELECT TOP (1) WITH TIES 
      position  = t.N,
      tokenSize = x.N,
      string    = substring(s.s1, t.N, x.N)
    FROM dbo.getshortstring8k(t.str1, t.str2) s -- @s1 & @s2 replaced with str1 & str2 
    CROSS JOIN dbo.tally t  
    CROSS JOIN dbo.tally x
    WHERE t.N between 1 and len(s.s1)
    AND   x.N between 1 and len(s.s1)
    AND   len(s.s1) - t.N - (x.N-1) >= 0
    AND   charindex(substring(s.s1, t.N, x.N), s.s2) > 0
    AND   charindex(' ',substring(s.s1, t.N, x.N)) = 0 -- exclude substrings with spaces
    ORDER BY x.N DESC
) lcss;

<强>结果:

str1        str2      max substring without spaces
----------- --------- ------------------------------
aabcdfbas   rikcdfva  cdf
aaab akuc   aaabir a  aaab
ab akuc     ab atr    ab

执行计划:

enter image description here

......没有种类或不必要的操作。只是速度。对于更长的字符串(例如50个字符+),我可以阅读更快的技术here