SQL Server - 查找具有相同子字符串的记录

时间:2014-04-10 13:53:23

标签: sql sql-server substring

我继承了一个包含手工输入奖励编号的列的表。它已被许多人使用多年。奖项数字一般如下:

R01AR012345-01
R01AR012345-02
R01AR012345-03

每年都会分配奖励编号。因为过去有这么多不同的人参与其中,所以这些人的输入方式并不一致。例如,奖励序列可能如下所示:

R01AR012345-01
1 RO1AR012345-02
12345-03
12345-05A1
1234506

我找到的规则是返回该列中5个连续整数与另一个记录匹配的记录。

我知道如何匹配给定的字符串,但是当5个连续的整数未知时,我会感到茫然。

这是一个样本表,可以让我更清楚地寻找:

+----------------------+
|  table: AWARD        |
+-----+----------------+
| ID  | AWARD_NO       |
+-----+----------------+
| 12  | R01AR015123-01 |
+-----+----------------+
| 13  | R01AR015124-01 |
+-----+----------------+
| 14  | 15123-02A1     |
+-----+----------------+
| 15  | 1 Ro1XY1512303 |
+-----+----------------+
| 16  | R01XX099232-01 |
+-----+----------------+

在上表中,将返回以下ID:12,13,14,15

匹配的五个连续整数是:

12,13: 01512
12,14: 15123
12,15: 15123

在我们的具体案例中,ID 13是误报......但我们愿意根据具体情况处理这些问题。

这里是上表所需的回报:

+-----+-----+----------------+----------------+
| ID1 | ID2 | AWARD_NO_1     | AWARD_NO_2     |
+-----+-----+----------------+----------------+
| 12  | 13  | R01AR015123-01 | R01AR015124-01 |
+-----+-----+----------------+----------------+
| 12  | 14  | R01AR015123-01 | 15123-02A1     |
+-----+-----+----------------+----------------+
| 12  | 15  | R01AR015123-01 | 1 Ro1XY1512303 |
+-----+-----+----------------+----------------+

现在......我可以使用误报(比如12匹配13)和重复(因为如果12匹配14,那么14也匹配12)。我们正在查看类似18,000行的内容。在这种情况下,优化并不是必需的,因为它只需要运行一次。

5 个答案:

答案 0 :(得分:2)

这应该处理删除重复和大多数误报:

DECLARE @SPONSOR TABLE (ID INT NOT NULL PRIMARY KEY, AWARD_NO VARCHAR(50))

INSERT INTO @SPONSOR VALUES (12, 'R01AR015123-01')
INSERT INTO @SPONSOR VALUES (13, 'R01AR015124-01')
INSERT INTO @SPONSOR VALUES (14, '15123-02A1')
INSERT INTO @SPONSOR VALUES (15, '1 Ro1XY1512303')
INSERT INTO @SPONSOR VALUES (16, 'R01XX099232-01')

;WITH nums AS
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS [Num]
    FROM sys.objects
),
cte AS
(
    SELECT sp.ID,
           sp.AWARD_NO,
           SUBSTRING(sp.AWARD_NO, nums.Num, 5) AS [TestCode],
           SUBSTRING(sp.AWARD_NO, nums.Num + 5, 1) AS [FalsePositiveTest]
    FROM   @SPONSOR sp
    CROSS JOIN nums
    WHERE nums.Num < LEN(sp.AWARD_NO)
    AND SUBSTRING(sp.AWARD_NO, nums.Num, 5) LIKE '%[1-9][0-9][0-9][0-9][0-9]%'
--  AND SUBSTRING(sp.AWARD_NO, nums.Num, 5) LIKE '%[0-9][0-9][0-9][0-9][0-9]%'
)
SELECT sp1.ID AS [ID1],
       sp2.ID AS [ID2],
       sp1.AWARD_NO AS [AWARD_NO1],
       sp2.AWARD_NO AS [AWARD_NO2],
       sp1.TestCode
FROM cte sp1
CROSS JOIN @SPONSOR sp2
WHERE sp2.AWARD_NO LIKE '%' + sp1.TestCode + '%'
AND sp1.ID < sp2.ID
--AND 1 = CASE
--           WHEN (
--                   sp1.FalsePositiveTest LIKE '[0-9]'
--               AND sp2.AWARD_NO NOT LIKE
--                        '%' + sp1.TestCode + sp1.FalsePositiveTest + '%'
--                ) THEN 0
--           ELSE 1
--         END

输出

ID1   ID2   AWARD_NO1        AWARD_NO2        TestCode
12    14    R01AR015123-01   15123-02A1       15123
12    15    R01AR015123-01   1 Ro1XY1512303   15123
14    15    15123-02A1       1 Ro1XY1512303   15123

如果ID 14和15 匹配,我们也可能会对此进行更正。

编辑:

根据@Serpiton的评论,我注释了[FalsePositiveTest]字段的创建和使用,因为将SUBSTRING上的LIKE子句中的初始字符范围更改为[1-9]实现了相同的目标,更有效率。但是,此更改假定没有有效的Award#将以0开头,我不确定这是否是一个有效的假设。因此,我保留了原始代码,但只是注释掉了。

答案 1 :(得分:0)

您希望在where子句中使用LIKE命令,并使用模式查找5个数字。请参阅此帖子here

可能有更好的方法来表示这一点,但下面的示例在列值中的任何位置查找彼此相邻的0-9中的5位数。但这可能会很慢......

Select *
from blah
Where column LIKE '%[0-9][0-9][0-9][0-9][0-9]%'

答案 2 :(得分:0)

创建一个sql server函数来提取5个数字,然后在查询中使用该函数。

也许是这样的:

 select GetAwardNumber(AwardNumberField) as AwardNumber
 from Awards
 group by GetAwardNumber(AwardNumberField)

答案 3 :(得分:0)

我不会发布代码,而是关于如何执行此操作的想法。

首先,您需要创建一个表值函数,该函数将返回大于5个字符的字符串中的所有数字序列。 (SO上有例子) 因此,对于每个条目,您的函数将返回一个数字列表。

之后,查询将简化为:

;with res as (
  select 
    id,                             -- hopefully there is an id on your table
    pattern                         -- pattern is from the list of patterns the udtf returns
  from myTable
  cross apply udtf_custom(myString) -- myString is the string you need to split
)
select 
  pattern
from res
group by pattern
having count(distinct id)>1

我必须注意,这是出于示例目的,应该涉及一些编码和测试,但这应该是它的故事。

祝你好运,希望有所帮助。

答案 4 :(得分:0)

这是我最终的结果:

SELECT a1.ID as AWARD_ID_1, a2.ID as AWARD_ID_2, a1.AWARD_NO as Sponsor_Award_1, a2.AWARD_NO as Sponsor_Award_2
FROM AWARD a1
LEFT OUTER JOIN AWARD a2
    ON SUBSTRING(a1.AWARD_NO,PATINDEX('%[0-9][0-9][0-9][0-9][0-9]%',a1.AWARD_NO + '1'),5) = SUBSTRING(a2.AWARD_NO,PATINDEX('%[0-9][0-9][0-9][0-9][0-9]%',a2.AWARD_NO + '1'),5)
WHERE
    a1.AWARD_NO <> '' AND a2.AWARD_NO <> ''
    AND a1.ID <> a2.ID
    AND a1.AWARD_NO LIKE '%[0-9][0-9][0-9][0-9][0-9]%' AND a2.AWARD_NO LIKE '%[0-9][0-9][0-9][0-9][0-9]%'

可能五个字符的第一个子字符串可能不匹配(当它们应该生成匹配时),但它对我们足够接近。 : - )