Sql 将列值匹配为前缀

Sql 将列值匹配为前缀,sql,sql-server,Sql,Sql Server,我正在SQL Server表中存储字符串前缀,我想看看这些值中是否有一个是给定参数值的有效前缀 e、 g.假设我有一个电话禁止通话列表,其中包含一个条目,禁止所有电话呼叫以“1425123”开头的号码,而不是插入10000个号码(14251230000到14251239999),它会存储前缀 像这样: CREATE TABLE Prefixes ( Value varchar(10) ) CREATE INDEX IX_Value UNIQUE Prefixes ( Value )

我正在SQL Server表中存储字符串前缀,我想看看这些值中是否有一个是给定参数值的有效前缀

e、 g.假设我有一个电话禁止通话列表,其中包含一个条目,禁止所有电话呼叫以“
1425123
”开头的号码,而不是插入10000个号码(
14251230000
14251239999
),它会存储前缀

像这样:

CREATE TABLE Prefixes (
    Value varchar(10)
)

CREATE INDEX IX_Value UNIQUE Prefixes ( Value )
这样评价:

DECLARE @value varchar(10) = 'foobar'

SELECT
    *
FROM
    Prefixes
WHERE
    @value LIKE ( Value + '%' );
当我在SQLServerManagementStudio中的Azure SQL中运行它时,它表示正在执行索引扫描。Azure SQL S1数据库上大约有70000个条目,执行查询需要200到500毫秒。该工具不建议对索引进行任何改进以提高性能

为了进行比较,精确相等匹配(
Value=@Value
)使用索引查找,并且几乎立即发生

200-500毫秒对于我的应用程序来说太慢了

一种方法是使用Trie将查找移动到我的应用程序代码中,以实现高效的前缀搜索(这会带来同步问题),但另一种方法是将查询更改为如下内容:

DECLARE @v1 varchar(1) = LEFT( @value, 1 )
DECLARE @v2 varchar(2) = LEFT( @value, 2 )
DECLARE @v3 varchar(3) = LEFT( @value, 3 )
DECLARE @v4 varchar(4) = LEFT( @value, 4 )
DECLARE @v5 varchar(5) = LEFT( @value, 5 )
DECLARE @v6 varchar(6) = LEFT( @value, 6 )
DECLARE @v7 varchar(7) = LEFT( @value, 7 )
DECLARE @v8 varchar(8) = LEFT( @value, 8 )
DECLARE @v9 varchar(9) = LEFT( @value, 9 )

SELECT
    *
FROM
    Prefixes
WHERE
    Value = @v1 OR
    Value = @v2 OR
    Value = @v3 OR
    Value = @v4 OR
    Value = @v5 OR
    Value = @v6 OR
    Value = @v7 OR
    Value = @v8 OR
    Value = @v9
当我运行这个程序时,速度要快得多(使用索引搜索),但感觉像是黑客攻击,但因为我知道长度小于10个字符,所以我可以接受它。。。现在


有更好的办法吗?SQL Server是否有一种方法可以在内部进行前缀匹配(即,在我的最后一个示例中使用相同的逻辑,但不使用重复且脆弱的SQL)?

第一个选项之所以慢,是因为它不是,因为您正在修改where子句中的
前缀.Value

因此,不可能利用该指数

您建议的解决方案很好(尽管缺少长度为10的前缀)

我唯一要指出的是,您肯定更愿意使用
EXISTS
查询?一旦你找到了一个匹配项,那么你就完成了;不需要再找了。而且中的
更简洁


如果这真的很重要,你可以考虑使用。(不幸的是,我自己从未使用过它,因此无法进一步帮助。)我知道这需要更多的工作,但可能是合理的。它过去需要运行额外的服务;我不知道情况是否仍然如此


编辑

在以下情况下,从中借款仍然是低效的:

  • @Value='9999999'
    并且不匹配任何前缀
  • 原因是所有
    前缀.Value<'99999999'
  • 但是没有一个与筛选器
    @值匹配,比如value+'%'
  • 因此,查询仍然必须扫描所有行
不过,我确实认为(通过一些调整)可以通过始终首先获取
,然后检查该值是否与
@value like value+'%'
特别匹配来提高效率。问题是,您需要首先保证
前缀
不包含任何“冗余”值(或者至少可以使用标志轻松过滤掉冗余值)

我所说的冗余是指任何本身无效的
值,因为它以现有的较短前缀开头

然后可以使用以下查询

SELECT  *
FROM    (
        SELECT  TOP 1 Value as PossiblePrefix
        FROM    Prefixes
                /* WHERE can leverage index;
                   but requires NO redundant Prefixes.Value rows
                   so that it returns only ONE possible prefix that
                   has a chance of matching @Value.*/
        WHERE   Value <= @Value
        ORDER BY Value DESC
        ) pp
WHERE   @Value LIKE pp.PosisblePrefix + '%'
选择*
从(
选择TOP 1值作为可能的Prefix
来自前缀
/*哪里可以利用指数;
但不需要冗余前缀。值行
所以它只返回一个可能的前缀
有机会匹配@Value*/

其中Value这是一个辅助数字表可以帮助的

因为您只需要
1-10
,所以我在查询中内联了一个,而不是假设存在一个

如果您有或可以创建一个永久数字表,则可以通过将派生表
V
替换为对永久数字表的引用来缩短代码

SELECT IIF(EXISTS (SELECT *
                   FROM   (VALUES(1),(2),(3),
                                 (4),(5),(6),
                                 (7),(8),(9),(10)
                          ) V(number)
                          JOIN Prefixes P WITH(FORCESEEK)
                            ON P.Value = LEFT(@value, number)
                   WHERE  number <= LEN(@value)), 1, 0) AS PrefixExists 
选择IIF(存在)(选择*
从(值(1)、(2)、(3),
(4),(5),(6),
(7),(8),(9),(10)
)V(数字)
用(FORCESEEK)连接前缀P
在P.值=左(@Value,number)

其中数字是70000个条目,这是真实的吗?其中有多少是完整的数字,有多少是前缀。还有多少条目是多余的?例如,如果前缀包含
'1'
,那么可能会有数百个甚至数千个以
1
开头的条目被忽略,因为
'1'
已经是
'14251230000'
。很好!我是否可以建议使用
set showplan\u text on
来获得比图像更有用的查询计划?
SELECT IIF(EXISTS (SELECT *
                   FROM   (VALUES(1),(2),(3),
                                 (4),(5),(6),
                                 (7),(8),(9),(10)
                          ) V(number)
                          JOIN Prefixes P WITH(FORCESEEK)
                            ON P.Value = LEFT(@value, number)
                   WHERE  number <= LEN(@value)), 1, 0) AS PrefixExists 
  |--Compute Scalar(DEFINE:([Expr1014]=CASE WHEN [Expr1015] THEN (1) ELSE (0) END))
       |--Nested Loops(Left Semi Join, DEFINE:([Expr1015] = [PROBE VALUE]))
            |--Constant Scan
            |--Nested Loops(Inner Join, OUTER REFERENCES:([Union1010]))
                 |--Filter(WHERE:([Union1010]<=len([@value])))
                 |    |--Constant Scan(VALUES:(((1)),((2)),((3)),((4)),((5)),((6)),((7)),((8)),((9)),((10))))
                 |--Index Seek(OBJECT:([tempdb].[dbo].[Prefixes].[IX_Value] AS [P]), SEEK:([P].[Value]=substring([@value],(1),[Union1010])) ORDERED FORWARD)