2013-07-08 5 views
2

Можно ли искать строки типа 1.1.1 или 1.5.2 (многоуровневые абзацы) в SQL Server с использованием полного текста? My SQL выглядит следующим образом:sql-server полнотекстовый поиск строк многоуровневого списка

contains (MyTable.MyColumn,'"*5.1.1*"') 

Я уже попытался удалить номера из списка слов остановки или полностью вывести из строя список слов стоп. В результате строки, такие как 5.1 или 1.1, работают нормально (возможно, внутри них обрабатываются как числа?), Но для чисел с 2 точками результата все равно нет.

Есть ли способ избежать этих пунктирных строк/чисел или любого другого решения?

ответ

1

Периоды проблематичны в полнотекстовом поиске, поскольку они обычно рассматриваются как полные остановки между словами. Решением является замена периодов на другой символ, и вы можете сделать это с минимальными изменениями в приложении. Это довольно длинный сценарий, который позволяет вам идентифицировать проблему и прийти к решению. Вы можете перейти к версии «Короткий ответ», если все, что вам нужно, - это работа.

Настройка FullText схемы

SET ANSI_NULLS ON 
SET QUOTED_IDENTIFIER ON 
SET ANSI_PADDING ON 

CREATE TABLE [dbo].[FT_Test](
    [id] [int] IDENTITY(1,1) NOT NULL, 
    [TextData] [varchar](max) NOT NULL, 
CONSTRAINT [PK_FT_Test] PRIMARY KEY CLUSTERED 
(
    [id] ASC 
) ON [PRIMARY] 
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 

GO 
CREATE FULLTEXT CATALOG [ft_default] WITH ACCENT_SENSITIVITY = ON 
CREATE FULLTEXT INDEX ON [dbo].[FT_Test] KEY INDEX [PK_FT_Test] ON ([ft_default]) 
    WITH (CHANGE_TRACKING AUTO) 
ALTER FULLTEXT INDEX ON [dbo].[FT_Test] ADD ([TextData]) 
ALTER FULLTEXT INDEX ON [dbo].[FT_Test] ENABLE 

Проверьте Sql Server версии

Этот сценарий разработан вокруг Sql Server 2012, но должно быть приложение к 2008 году, а также. Разрыв слова существенно изменился между Sql 2008 и Sql 2012 (по крайней мере, для языка id 1033 - US английский). Главный вывод в том, что 1-2-3 нарушается в 1, 2, 3, 1-2-3, Nn1, NN2, NN3 (включая 1-2-3 новый)

go 
PRINT 'Version 14.0.4763.1000 is Sql Server 2012' 
EXEC master.sys.sp_help_fulltext_system_components @component_type = 'wordbreaker', @param=1033 

Sql Server анализирует ключевые слова полуинтеллектуально

К сожалению, это в настоящее время действует против нас. Мы получаем раздувание, так как одни и те же данные хранятся несколько раз и плохие результаты поиска.

go 
DELETE FROM ft_test 
INSERT INTO dbo.FT_Test (TextData) 
VALUES 
( '1.1.1 5.2.1, 7.1.1.34.69; 12.11.10.9.8 4.6 7/13/2013 15,456.345') 

WAITFOR DELAY '00:00:05' 
--Wait 5 seconds for ft index to populate 

SELECT ft_test.*, ft_content.display_term, ft_content.occurrence_count 
FROM sys.dm_fts_index_keywords_by_document(DB_ID(), OBJECT_ID('ft_test')) ft_content 
     INNER JOIN dbo.FT_Test ON document_id = id 
ORDER BY id, keyword 
--Notice what is returned,the two digit numbers are identified, but the 1 digit numbers aren't (due to default stoplist). 
--Also, note that they are treated as distinct items and are broken up. 4.6 does show up because it is a decimal number. 
--the nn* display_terms are standardized numeric (also, note how the date got standardized as dd20120713 in addition to 7/13/2013) 

SELECT * 
FROM ft_test 
WHERE CONTAINS (*, '"5.2*"') -- No results, 5 and 2 are in default stopword list. 

SELECT * 
FROM ft_test 
WHERE CONTAINS (*, '"12.11*"') -- periods are hard breaks, so this doesn't work either 

Создать Настроенный stoplist индексировать однозначных цифр

Одиночные цифры обычно не стоит в отношении полнотекстового поиска, но мы нуждаемся в них. Мы будем использовать стандартный стоп-лист по умолчанию в качестве основы.

CREATE FULLTEXT STOPLIST [no_numbers] 
FROM SYSTEM STOPLIST 
AUTHORIZATION [dbo]; 
go 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '0' LANGUAGE 'English'; 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '1' LANGUAGE 'English'; 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '2' LANGUAGE 'English'; 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '3' LANGUAGE 'English'; 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '4' LANGUAGE 'English'; 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '5' LANGUAGE 'English'; 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '6' LANGUAGE 'English'; 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '7' LANGUAGE 'English'; 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '8' LANGUAGE 'English'; 
ALTER FULLTEXT STOPLIST [no_numbers] DROP '9' LANGUAGE 'English'; 
GO 

воссоздавать индекс Полный текст на основе от нового stoplist

Это помогает некоторым и заставляет нас ближе к где мы хотим быть.

DROP FULLTEXT INDEX ON dbo.FT_Test 

CREATE FULLTEXT INDEX ON [dbo].[FT_Test] (TextData) KEY INDEX [PK_FT_Test] ON ([ft_default]) 
    WITH (CHANGE_TRACKING AUTO, STOPLIST = [no_numbers]) 

WAITFOR DELAY '00:00:05' 
--Wait 5 seconds for ft index to populate 

SELECT ft_test.*, ft_content.display_term, ft_content.occurrence_count 
FROM sys.dm_fts_index_keywords_by_document(DB_ID(), OBJECT_ID('ft_test')) ft_content 
     INNER JOIN dbo.FT_Test ON document_id = id 
ORDER BY id, keyword 

--Progress, now single digits are showing up 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1 1 14.123') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('5.2.1.1.14') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1.1.3 ') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('2.2.3.3') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('6.0 88.00.00') 

--This works in the first 3 cases, but doesn't work for 2.2 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '1.1.1*' ) ct ON ct.[key] = ft_test.id 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '2.2.3.3*' ) ct ON ct.[key] = ft_test.id 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '2.2.3*' ) ct ON ct.[key] = ft_test.id 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '2.2*' ) ct ON ct.[key] = ft_test.id 

--Double quoting makes it match more stuff, but still is broken. 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"1.1.1*"' ) ct ON ct.[key] = ft_test.id 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"1.1*"' ) ct ON ct.[key] = ft_test.id 

Мы определенно ближе, но приведенный выше пример 2.2 * вызывает раздражение. Это разбираемый как десятичное число:

declare @stoplistId INT 
SET @stoplistid = (SELECT stoplist_id FROM sys.fulltext_stoplists WHERE name ='no_numbers') 
SELECT * FROM sys.dm_fts_parser('"1.1.1*"', 1033,@stoplistId, 0) 
SELECT * FROM sys.dm_fts_parser('1.1.1*', 1033,@stoplistId, 0) 
SELECT * FROM sys.dm_fts_parser('"1.1*"', 1033,@stoplistId, 0) 
SELECT * FROM sys.dm_fts_parser('1.1*', 1033,@stoplistId, 0) 

Какие другие символы в качестве разделителей являются потенциальными?

Давайте попробуем несколько и посмотрим, если выпрыгнете.Мы могли бы попробовать что-то вроде «XXXDOTXXX», но гораздо чище держать его как можно одиноч.

INSERT INTO dbo.FT_Test (TextData) 
VALUES 
( '1-1-1 [email protected]@2 3#3#3 4$4$4 5%5%5 6^6^6 7&7&7 8*8*8 9=9=9 10_10_10 11|11|11 12:12:12 12:12:12:12 13"13"13" 14~14~14 15`15`15') 

SELECT ft_test.*, ft_content.display_term, ft_content.occurrence_count 
FROM sys.dm_fts_index_keywords_by_document(DB_ID(), OBJECT_ID('ft_test')) ft_content 
     INNER JOIN dbo.FT_Test ON document_id = id WHERE textdata LIKE '1-1-1%' 
ORDER BY id, keyword 

DELETE FROM ft_test WHERE textdata LIKE '%3#3#3%' 

Похоже, что дефис, подчеркивание или обратный отсчет могут работать. Давайте рассмотрим их более подробно.

INSERT INTO dbo.FT_Test (TextData) 
VALUES ('3`3`3`4 1`2`3 6`1`2`3`4 ') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('5-5-5-6 2-3-4 6-1-2-3-4-5') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('6_6_6_7 3_4_5 7_1_2_3_4_5_6') 


SELECT ft_test.*, ft_content.display_term, ft_content.occurrence_count 
FROM sys.dm_fts_index_keywords_by_document(DB_ID(), OBJECT_ID('ft_test')) ft_content 
     INNER JOIN dbo.FT_Test ON document_id = id WHERE textdata LIKE '3`3%' OR TextData LIKE '5-5%' OR textdata LIKE '6_6%' 
ORDER BY id, keyword 
--Hyphen isn't looking good now, it gets stored 3 times, as numbers, as individual digits and as a full string. 

--Let's try backquote: 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"3`3*"' ) ct ON ct.[key] = ft_test.id 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"1`2`3*"' ) ct ON ct.[key] = ft_test.id 

-- these match anything with a single 6... not good... 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"6`*"' ) ct ON ct.[key] = ft_test.id 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '6`*' ) ct ON ct.[key] = ft_test.id 

--the backquote is getting dropped when it's parsed 
declare @stoplistId INT 
SET @stoplistid = (SELECT stoplist_id FROM sys.fulltext_stoplists WHERE name ='no_numbers') 
SELECT * FROM sys.dm_fts_parser('"6`*"', 1033,@stoplistId, 0) 
SELECT * FROM sys.dm_fts_parser('6`*', 1033,@stoplistId, 0) 


--Underscore is just about all we have left. 
declare @stoplistId INT 
SET @stoplistid = (SELECT stoplist_id FROM sys.fulltext_stoplists WHERE name ='no_numbers') 
SELECT * FROM sys.dm_fts_parser('"2_*"', 1033,@stoplistId, 0) 
SELECT * FROM sys.dm_fts_parser('2_*', 1033,@stoplistId, 0) 
SELECT * FROM sys.dm_fts_parser('2_2*', 1033,@stoplistId, 0) 
SELECT * FROM sys.dm_fts_parser('2_2*', 1033,@stoplistId, 0) 
SELECT * FROM sys.dm_fts_parser('2_2_*', 1033,@stoplistId, 0) 
SELECT * FROM sys.dm_fts_parser('2_2_*', 1033,@stoplistId, 0) 


INSERT INTO dbo.FT_Test (TextData) 
VALUES ('6_6_66_7 77_6_6_6') 


-- 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"6_*"' ) ct ON ct.[key] = ft_test.id  WHERE textdata LIKE '%[_]%' 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '6_*' ) ct ON ct.[key] = ft_test.id  WHERE textdata LIKE '%[_]%' 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '6_6*' ) ct ON ct.[key] = ft_test.id  WHERE textdata LIKE '%[_]%' 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '2_3*' ) ct ON ct.[key] = ft_test.id  WHERE textdata LIKE '%[_]%' 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '6_6*' ) ct ON ct.[key] = ft_test.id  WHERE textdata LIKE '%[_]%' 

SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '6_6_6_7*' ) ct ON ct.[key] = ft_test.id  WHERE textdata LIKE '%[_]%' 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"6_6_6_7*"' ) ct ON ct.[key] = ft_test.id WHERE textdata LIKE '%[_]%' 

SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"6_6_6_*"' ) ct ON ct.[key] = ft_test.id  WHERE textdata LIKE '%[_]%' 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"6_6_6*"' ) ct ON ct.[key] = ft_test.id  WHERE textdata LIKE '%[_]%' 
SELECT * FROM ft_test LEFT JOIN CONTAINSTABLE(ft_test, *, '"6_6_*"' ) ct ON ct.[key] = ft_test.id  WHERE textdata LIKE '%[_]%' 

Короткий ответ

Заменить периоды подчеркивания

Подчеркивание путь. Он рассматривается как символ, а не как пунктуация. Sql Server может создавать полный текстовый индекс в вычисленном столбце. Это позволит нам использовать формулу для «исправления» данных, индексации и запроса без дополнительной памяти (а также с минимальными издержками). Вам нужно будет изменить приложение на запрос «1_2_3» вместо «1.2.3».

--naive implementation 
ALTER TABLE ft_test ADD [TextData_FT1] AS ([textdata]+' '+replace([TextData],'.','_')) 

--strip all characters. You can customize to get pull out only the paragraph numbers 
ALTER TABLE ft_test ADD [TextData_FT2] AS (REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(UPPER([TextData]) 
,'A', ' '),'B', ' '),'C', ' '),'D', ' '),'E', ' '),'F', ' '), 
'G', ' '),'H', ' '),'I', ' '),'J', ' '),'K', ' '),'L', ' '),'M', ' '),'N', ' '), 
'O', ' '),'P', ' '),'Q', ' '),'R', ' '),'S', ' '),'T', ' '),'U', ' '),'V', ' '), 
'W', ' '),'X', ' '),'Y', ' '),'Z', ' '), '.','_') , ' ',' ') 
) 


--Add computed columns to FT index 
ALTER FULLTEXT INDEX ON [dbo].[FT_Test] ADD ([TextData_FT1]) 
ALTER FULLTEXT INDEX ON [dbo].[FT_Test] ADD ([TextData_FT2]) 


DELETE FROM dbo.FT_Test 

INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1 This is the chapter title') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1.1 Section heading') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1.1.1 paragraph 1 is very interesting') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1.1.2 paragraph two is better') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1.2 Another Section') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1.2.1 Foobar qwerty loren ipsum') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1.2.2 Foobar2 qwerty2 loren ipsum 12 items ') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('1.2.12 Foobar2 qwerty2 loren ipsum ') 
INSERT INTO dbo.FT_Test (TextData) 
VALUES ('2.2.17 sql server is great. ') 

--naive implementation 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft1, '"1_1*"') 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft1, '1*') 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft1, '2*') -- 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft1, '"1_1_2*"') 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft1, '1_1_2*') 

--only index the paragraph identifiers 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft2, '"1_1*"') 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft2, '1*') 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft2, '2*') -- 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft2, '"1_1_2*"') 
SELECT * FROM ft_Test WHERE CONTAINS(TextData_ft2, '1_1_2*') 
+0

спасибо. но на самом деле кажется немного дорогим. Неужели нет способа позволить полнотекстовому движку знать, как относиться к этой точке по-разному? – user250773

+0

Короче написав собственный парсер (который вы не хотите делать, поверьте мне), нет. Этот подход на самом деле не добавляет столько накладных расходов, поскольку формула для замены символов оценивается только при индексировании строки, а не при ее поиске. Анализаторы являются частью функциональности поиска Windows и отлично подходят для почтовых сообщений, текстовых документов и т. Д. Для индексирования доменных индексов у них есть недостатки. – StrayCatDBA

Смежные вопросы