【问题标题】:t-sql "LIKE" and Pattern Matchingt-sql "LIKE" 和模式匹配
【发布时间】:2011-11-02 08:04:40
【问题描述】:

我发现了一个小烦恼,我想知道如何解决......

在一个简化的例子中,假设我需要返回“TEST B-19”和“TEST B-20”

我有一个 where 子句,如下所示:

where [Name] LIKE 'TEST B-[12][90]'

它可以工作...除非有我不想要的“TEST B-10”或“TEST-B29”值。

我宁愿不同时做这两种情况,因为在更复杂的情况下会变得令人望而却步。

我试过了:

where [Name] LIKE 'TEST B-[19-20]'

但这当然行不通,因为它正在寻找单个字符...

想法?同样,这是一个非常简单的示例,我正在寻找方法来获取从 16 到 32 或 234 到 459 的范围,而不获取所有可以创建的额外值。

已编辑以包含测试示例...

您可能会在该字段中看到“TEXAS 22”或“THX 99-20-110-B6”或“E-19”或“SOUTHERN B”或“122 FLOWERS”。数字的出现很常见,但不是一个固定的规则,连字符、数字、字符、顺序等绝对没有通用模式。

【问题讨论】:

  • 例如,您可以使用 CLR 集成和正则表达式来利用交替运算符 |

标签: tsql pattern-matching sql-like


【解决方案1】:

我会将Name 列分为文本部分和数字部分,并将数字部分转换为整数,然后检查该值是否在值之间。比如:

where cast(substring([Name], 7, 2) as integer) between 19 and 20

当然,如果 [Name] 的可能结构要复杂得多,您就必须计算 7 和 2 的值,而不是硬编码它们......

编辑:如果要先过滤掉不符合模式的,请执行以下操作:

where [Name] LIKE '%TEST B-__%'
and cast(substring([Name], CHARINDEX('TEST B-', [Name]) + LEN('TEST B-'), 2) as integer) between 19 and 20

也许使用CHARINDEX 代替最上面第二行的 LIKE 会更快,特别是如果您在计算值上放置一个索引,但是...这只是优化... :)

编辑:测试程序。给定以下数据:

jajajajajajajTEST B-100
jajajajajajajTEST B-85
jajajajjTEST B-100
jajjajajTEST B-100
jajajajajajajTEST B-00
jajajajaTEST B-100
jajajajajajajEST B-99
jajajajajajajTEST B-100
jajajajajajajTEST B-19
jajajajjTEST B-100
jajjajajTEST B-120
jajajajajajajTEST B-00
jajajajaTEST B-150
jajajajajajajEST B-20
TEST B-20asdfh asdfkh

查询返回以下行:

jajajajajajajTEST B-19
TEST B-20asdfh asdfkh

【讨论】:

  • 很好的解决方案,但您已经预料到了这个问题。 [Name] 中的值有很多变化,其中一些甚至可能根本没有数字。我必须首先将查询过滤为仅包含“TEST B-##”结果,然后触发它,这比我希望的要多一步。
  • CHARINDEX() 返回 1,因此它试图将 'TE' 转换为整数。好像我遗漏了什么,我知道你要去哪里,但这个查询不会编译。
  • 我会尝试编译它。我已经编辑了它,我忘记将字符串的长度添加到索引中...在子字符串的第二个参数中添加了一个 + LEN('TEST B-')...我将在获取它时启动 SQL 管理器编译...
【解决方案2】:

这可以接受吗:

WHERE [Name] IN ( 'TEST B-19', 'TEST B-20' )

值列表可以来自子查询,例如:

WHERE [Name] IN ( SELECT [Name] FROM Elsewhere WHERE ... )

【讨论】:

  • 这适用于简单的情况,但更复杂的情况会有点冗长。子查询解决方案也可以工作,但大多数情况下它只是将问题推到子查询级别。这是我想要合并模式匹配的 where 子句。感谢您的建议!
【解决方案3】:

在没有测试数据的情况下,我自己生成了。我刚刚删除了 Test B- 前缀,转换为 int 并做了一个 Between

With Numerals As
(
    Select top 100 row_number() over (order by name)  TestNumeral
    from sys.columns
),
TestNumbers AS
(
    Select 'TEST B-' + Convert (VarChar, TestNumeral) TestNumber
    From Numerals
)
Select * 
From TestNumbers
Where Cast (Replace (TestNumber, 'TEST B-', '') as Integer) between 1 and 16

这给了我

TestNumber
-------------------------------------
TEST B-1
TEST B-2
TEST B-3
TEST B-4
TEST B-5
TEST B-6
TEST B-7
TEST B-8
TEST B-9
TEST B-10
TEST B-11
TEST B-12
TEST B-13
TEST B-14
TEST B-15
TEST B-16

然而,这意味着,如果您有不同的命名测试策略,您将不得不删除所有不同种类的前缀。

另一方面,如果您的测试编号是 TEST-Space-TestType-Hyphen-TestNumber 格式,您可以使用 PatIndex 和 SubString

With Numerals As
(
    Select top 100 row_number() over (order by name)  TestNumeral
    from sys.columns
),
TestNumbers AS
(
    Select 'TEST B-' + Convert (VarChar, TestNumeral) TestNumber
    From Numerals
    Where TestNumeral Between 10 and 19
    UNION
    Select 'TEST A-' + Convert (VarChar, TestNumeral) TestNumber
    From Numerals
    Where TestNumeral Between 20 and 29
)
Select *
From TestNumbers
Where Cast (SubString (TestNumber, PATINDEX ('%-%', TestNumber)+1, Len (TestNumber) - PATINDEX ('%-%', TestNumber)) as Integer) between 16 and 26

这应该会产生以下结果

TestNumber
-------------------------------------
TEST A-20
TEST A-21
TEST A-22
TEST A-23
TEST A-24
TEST A-25
TEST A-26
TEST B-16
TEST B-17
TEST B-18
TEST B-19

您的所有示例似乎最后都有测试编号。因此,如果您可以创建一个模式表,然后使用 LIKE 语句进行 JOIN,您也许可以使其工作。这是一个例子:

;
With TestNumbers As
(
      select 'E-1' TestNumber
union select 'E-2'
union select 'E-3'
union select 'E-4'
union select 'E-5'
union select 'E-6'
union select 'E-7'
union select 'SOUTHERN B1'
union select 'SOUTHERN B2'
union select 'SOUTHERN B3'
union select 'SOUTHERN B4'
union select 'SOUTHERN B5'
union select 'SOUTHERN B6'
union select 'SOUTHERN B7'
union select 'Southern CC'
union select 'Southern DD'
union select 'Southern EE'
union select 'TEST B-1'
union select 'TEST B-2'
union select 'TEST B-3'
union select 'TEST B-4'
union select 'TEST B-5'
union select 'TEST B-6'
union select 'TEST B-7'
union select 'TEXAS 1'
union select 'TEXAS 2'
union select 'TEXAS 3'
union select 'TEXAS 4'
union select 'TEXAS 5'
union select 'TEXAS 6'
union select 'TEXAS 7'
union select 'THX 99-20-110-B1'
union select 'THX 99-20-110-B2'
union select 'THX 99-20-110-B3'
union select 'THX 99-20-110-B4'
union select 'THX 99-20-110-B5'
union select 'THX 99-20-110-B6'
union select 'THX 99-20-110-B7'
union select 'Southern AA'
union select 'Southern CC'
union select 'Southern DD'
union select 'Southern EE'
),
Prefixes as
(
    Select 'TEXAS ' TestPrefix
    Union Select 'THX 99-20-110-B'
    Union Select 'E-'
    Union Select 'SOUTHERN B'
    Union Select 'TEST B-'
)
Select TN.TestNumber
From TestNumbers TN, Prefixes P
Where 1=1 
And TN.TestNumber Like '%' + P.TestPrefix + '%'
And Cast (REPLACE (Tn.TestNumber, p.TestPrefix, '') AS INTEGER) between 4 and 6

这会给你

TestNumber
----------------
E-4
E-5
E-6
SOUTHERN B4
SOUTHERN B5
SOUTHERN B6
TEST B-4
TEST B-5
TEST B-6
TEXAS 4
TEXAS 5
TEXAS 6
THX 99-20-110-B4
THX 99-20-110-B5
THX 99-20-110-B6

(15 row(s) affected)

【讨论】:

  • 感谢您的快速回复。我也喜欢 cast(replace()) 解决方案,我只是希望 LIKE 语句中有一种方法。答案可能是它不能在那里完成,在这种情况下这肯定就足够了。
  • 看起来不像 LIKE 从文档中向我提供类似的东西,不,抱歉...msdn.microsoft.com/en-us/library/ms179859.aspx
  • 是的,测试数据应该包括各种长度、格式等。你可能会看到“TEXAS 22”或“THX 99-20-110-B6”或“E-19”或“ SOUTHERN B" 在那个领域。这将是能够在括号之间的 LIKE 语句中表示两位数字范围的美妙之处。
  • 感谢所有输入 Raj。我选择了 Erik 的解决方案,因为它解决了 WHERE 子句中的问题,因为它是更大查询的一部分。你的灵魂里肯定有一些很好的技巧,我以后会参考。
【解决方案4】:

无论是否使用通配符,您仍然必须在每次要更改范围定义时编辑查询。如果您总是处理一个范围(并且它并不总是相同的范围),您可能会使用参数。例如:

注意:由于某种原因(这在许多其他帖子中也发生过),当我尝试发布以“声明”开头的代码时,SO 挂起并超时。我已经在 meta 上报告了它,但没有人可以复制它(包括我)。这又发生了,所以我取下了“D”,现在它可以工作了。我明天再来,它会让我重新戴上“D”。

DECLARE @min varchar(5)
DECLARE @max varchar(5)

SET @min = 'B-19'
SET @max = 'B-20'

SELECT
   ...
WHERE NAME BETWEEN @min AND @max

您应该避免像其他人建议的那样格式化 [NAME](在其上使用函数)——这样,您的搜索可以受益于其上的索引。

无论如何——您可能会重新考虑您的表结构。听起来'TEST B-19'是类别('TEST')+子类别('B')+实例('19')的复合(非标准化)值。把它放在一个有 4 列的查找表中(id 是第一列),然后在任何需要输出复合值的查询中通过 id 加入它。这将使搜索和索引变得更加容易和快捷。

【讨论】:

  • 字段中的名称是实体,并且遵循本地化约定(一般规则 - 并非适用于所有情况),但我的数据是全局数据集,除非我为每个地区,真的没有我可以一刀切的规则。
猜你喜欢
  • 1970-01-01
  • 2015-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-12
  • 2011-07-23
相关资源
最近更新 更多