【问题标题】:Full Text Search of URL field sql serverURL字段sql server的全文搜索
【发布时间】:2016-11-18 00:03:50
【问题描述】:

目标:返回所有以"https://mywebsite.domain.com/as/product/4/"开头的URL

给定:

  • 在 URL 字段中应用了全文搜索。
  • SQL Server 版本:2014。
  • 20+ 百万行

网址

https://mywebsite.domain.com/as/product/1/production
https://mywebsite.domain.com/as/product/2/items
https://mywebsite.domain.com/as/product/1/affordability
https://mywebsite.domain.com/as/product/3/summary
https://mywebsite.domain.com/as/product/4/schedule
https://mywebsite.domain.com/as/product/4/resources/summary

查询 1:

WHERE CONTAINS (URL, 'https://mywebsite.domain.com/as/product/4')

结果:

All records returned

查询2(阅读MSDN article后添加“*”)

WHERE CONTAINS (URL, '"https://mywebsite.domain.com/as/product/4*"')

结果:

No records returned

任何帮助将不胜感激。

【问题讨论】:

  • 是 url 列总是以https://mywebsite.domain.com/as/product/ 或至少以https://mywebsite.domain.com 开头?
  • 是的,每条记录都以协议和域开头。
  • 好的,但是域和协议是固定的?
  • 正确,它们是固定的。
  • 还有/as/product 总是存在吗?还是可能不同?

标签: sql sql-server tsql full-text-search


【解决方案1】:

您可以使用 CONTAINSLIKE 子查询来仅匹配开头:

SELECT * 
FROM (
SELECT * 
FROM myTable WHERE CONTAINS (URL, '"https://mywebsite.domain.com/as/product/4/"')
) AS S1 
WHERE S1.URL LIKE 'https://mywebsite.domain.com/as/product/4/%' 

这样,SLOW LIKE 运算符查询将针对较小的记录集运行

EDIT1:(如果WHERE CONTAINS (URL, '"https://mywebsite.domain.com/as/product/4/"') 没有过滤值)

经过大量搜索。问题出在 / 中。正斜杠不包含在 Noise Words 文件中,但我猜它被归类为分隔符或分词器,因此不可搜索。

阅读这些主题:

EDIT2:

我找到了一个建议的解决方案

/ 被认为是英文分词器您可以从注册表中更改它

  • 导航到注册表值 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<InstanceRoot>\MSSearch\Language\engHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<InstanceRoot>\MSSearch\Language\enu
  • 清除 WBreakerClass 的值。

Sql server 将https://mywebsite.domain.com/as/product/4 视为一个词。

注意:以上两条路径我都假设您使用英语作为分词器。

MSDN Topic 中了解更多关于 Word Breaker 的信息

【讨论】:

  • 正如MtwStarkRafael 的回答所评论的那样,我们应该在% 之前添加一个斜杠,否则它还会发现44、4xyx ... OP 需要.../4/%
  • @WorkSmarter 你不需要任何全文搜索,删除全文索引,在你的 url 列上放置一个标准的非聚集索引,并且只使用 LIKE 运算符。 CONTAINS 只是不必要的开销。由于 url 列 LIKE FixedPattern+% 上的索引是通过索引搜索执行的,因此您无法进一步改进它(可能除非您将该索引本身设置为集群)请参阅我的答案以获取更多详细信息
  • 这种方法的缺点是 SQL Server performs the LIKE scan first, and then merges it with the results of the CONTAINS - 这意味着除了全文查询之外,慢速 LIKE 运算符始终首先针对所有行运行。
【解决方案2】:

使用Like 运算符:

WHERE URL LIKE 'https://mywebsite.domain.com/as/product/4%'

% 是通配符。这应该返回所有以匹配第一个通配符 % 的模式开头的记录。

【讨论】:

  • 让我第一个欢迎您来到 StackOverflow。较小的记录集与“喜欢”配合得很好。但是,在处理数百万条记录时,“Like”运算符的性能变为 issue
  • @WorkSmarter 在索引列上,使用LIKE 运算符从头开始搜索字符串也非常快,看看我的答案
  • 您应该在% 之前添加一个斜杠,否则它还会找到 44、4xyx ... OP 需要 .../4/%
  • @HenningKoehler 如果没有聚集索引,也没有聚集索引,也没有主键,索引也会执行得非常快。索引本身就是 b 树,是否聚集,它只与数据的物理顺序有关。
  • @WorkSmarter:虽然在大多数情况下,您正在寻找文本的一些随机部分,FULLTEXT 索引会比LIKE 快得多,但LIKE 在您查找文本时很容易击败 FULLTEXT专门搜索索引字符串列的开头。你可以用不同的方式来做,但是 MtwStark 的第 2 和第 3 方法得到了我的投票。它会更快、更容易设置,并为您节省大量资源和 FULLTEXT 的挫败感。
【解决方案3】:

如果您始终搜索字符串的开头,这将确保优化器可以使用索引。我假设 URL 是 VARCHAR

Declare @p varchar(500) ='https://mywebsite.domain.com/as/product/4'

Declare @maxChar char(1);
select @maxChar = max(ch)
from (
    select top(256) ch = char(row_number() over(order by (select null)) - 1)
    from sys.all_objects) t;
select @maxChar;

-- ..
WHERE URL > @p AND URL < @p + @maxChar

比较字符串时,Sql server 会在较短的字符串后面添加空格。见https://support.microsoft.com/en-us/kb/316626。根据 http://www.ietf.org/rfc/rfc1738.txthttp://www.ietf.org/rfc/rfc1738.txt 所有允许的 URL 符号都大于空格。因此搜索参数,例如'https://mywebsite.domain.com/as/product/4',将小于任何以该参数开头并超过参数长度的 URL。

【讨论】:

  • 为什么使用 CHAR(255)?
  • 好点,它可以是特定的排序规则。请参阅编辑后的答案,首先计算 @maxChar。
  • 您是否有使用比较运算符比较 varchar 的参考 &gt; &lt; &lt;= &gt;= = 以及它是否受益于索引?我真的很感兴趣。和
  • 搜索后。当使用Like 谓词搜索字符串Like 'xx%' 的开头时,它也使用索引。
  • 区别是'1' like '1%'为真,'1' &gt;'1'为假,OP只需要以搜索参数开头但不等于搜索参数的URL。
【解决方案4】:

对于类似的问题,我习惯了两种解决方案,具体取决于您的需求,主要是性能或资源或并发性......等等......

LIKE 运算符可能是您最好的朋友,也可以是非常大的桌子。

索引
首先,您需要为您的 url 列建立索引,处理 20+ 百万条记录并非易事, 索引它可能会花费您 1.5 - 2.0 Gb 的磁盘空间, 但您将在任何时间(毫秒)内得到您的查询

使用要搜索的列上的索引,LIKE FixedPattern+% 执行索引查找,您无法进一步改进它

第一个解决方案:

CREATE NONCLUSTERED INDEX [IX_URL] ON [url_table] ([url]);

DECLARE @Domain VARCHAR(100) = 'https://mywebsite.domain.com/'
DECLARE @Path VARCHAR(100) = 'as/product/'
DECLARE @Product VARCHAR(20) = '4'
DECLARE @LikeAll VARCHAR(100) = @Domain + @Path + @Product + '/%'

SELECT url
FROM url_table
WHERE url LIKE @LikeAll

第二种解决方案
第二个选项有点棘手但非常有效。
您说 url 的协议和域是固定的,您需要在之后搜索。
以下是一种技术,您可以对其进行微调以满足您的需求。
这个想法是向您的 url 表添加一个虚拟(计算)列,然后在其上添加一个索引。
这将大大减少索引维度并提高查询性能,代价是插入/更新中的计算开销非常小

ALTER TABLE url_table ADD path AS (SUBSTRING(url, 30, 4000));
CREATE NONCLUSTERED INDEX [IX_PATH] ON [url_table] ([path]);

DECLARE @Domain VARCHAR(100) = 'https://mywebsite.domain.com/'
DECLARE @Path VARCHAR(100) = 'as/product/'
DECLARE @Product VARCHAR(20) = '4'
DECLARE @LikeMid VARCHAR(100) = @Path + @Product + '/%' 

select @Domain + _path -- pay attention!!
FROM url_table
WHERE url LIKE @SrcAll

请注意,我们选择 @Domain + _path 而不是 url,以避免表访问并且只处理索引数据。

如果您需要 url_table 中的其他列,最好的选择是

declare @l table (id int primary key)
insert  into @l
select id 
from url_table 
where _path like @LikeMid

select url
from url_table
where id in (select id from @l)

非常快

第三种解决方案
这是第二个的变体。
在您的示例数据中,我看到路径包含 /product/ 后跟一个数字,我假设它是产品编号。 也许你可以考虑以下

ALTER TABLE url_table ADD _product AS (cast(substring(url,nullif(CHARINDEX('/product/',url,29)+9,9), CHARINDEX('/',url,nullif(CHARINDEX('/product/',url,29)+9,9))-nullif(CHARINDEX('/product/',url,29)+9,9)) as bigint));
CREATE NONCLUSTERED INDEX [IX_PRODUCT] ON [url] ([_product]);

select id, url
from url_table 
where _product = 4

这将生成一个整数类型产品编号的计算列,索引将只有 500Mb,并且对整数的查询将非常快。
此外,从 url_table 中选择所有列的开销非常小,因此您可以 SELECT * 几乎没有性能问题。

附: 您可以删除全文索引并节省空间和资源..

【讨论】:

    【解决方案5】:
    SELECT * FROM myTable WHERE URL LIKE 'https://mywebsite.domain.com/as/product/4%'
    

    【讨论】:

    • %之前需要额外的/
    猜你喜欢
    • 2015-09-10
    • 1970-01-01
    • 2010-09-06
    • 2012-07-04
    • 1970-01-01
    • 1970-01-01
    • 2011-06-10
    • 2011-10-01
    • 1970-01-01
    相关资源
    最近更新 更多