Estamos desarrollando una búsqueda como parte de un sistema más grande.
Tenemos Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Standard Edition (64-bit)
con esta configuración:
CREATE TABLE NewCompanies(
[Id] [uniqueidentifier] NOT NULL,
[Name] [nvarchar](400) NOT NULL,
[Phone] [nvarchar](max) NULL,
[Email] [nvarchar](max) NULL,
[Contacts1] [nvarchar](max) NULL,
[Contacts2] [nvarchar](max) NULL,
[Contacts3] [nvarchar](max) NULL,
[Contacts4] [nvarchar](max) NULL,
[Address] [nvarchar](max) NULL,
CONSTRAINT PK_Id PRIMARY KEY (Id)
);
Phone
es una cadena de dígitos estructurados separados por comas como"77777777777, 88888888888"
Email
es una cadena de correos electrónicos estructurados con comas similares"[email protected], [email protected]"
(o sin comas"[email protected]"
)Contacts1, Contacts2, Contacts3, Contacts4
son campos de texto donde los usuarios pueden especificar detalles de contacto en forma libre. Me gusta"John Smith +1 202 555 0156"
o"Bob, +1-999-888-0156, [email protected]"
. Estos campos pueden contener correos electrónicos y teléfonos que queremos buscar más.
Aquí creamos texto completo
-- FULL TEXT SEARCH
CREATE FULLTEXT CATALOG NewCompanySearch AS DEFAULT;
CREATE FULLTEXT INDEX ON NewCompanies(Name, Phone, Email, Contacts1, Contacts2, Contacts3, Contacts4, Address)
KEY INDEX PK_Id
Aquí hay una muestra de datos
INSERT INTO NewCompanies(Id, Name, Phone, Email, Contacts1, Contacts2, Contacts3, Contacts4)
VALUES ('7BA05F18-1337-4AFB-80D9-00001A777E4F', 'PJSC Azimuth', '79001002030, 78005005044', '[email protected], [email protected]', 'John Smith', 'Call only at weekends +7-999-666-22-11', NULL, NULL)
En realidad, tenemos alrededor de 100 mil de esos registros.
Esperamos que los usuarios puedan especificar una parte del correo electrónico como "@ gmail.com" y esto debería devolver todas las filas con direcciones de correo electrónico de Gmail en cualquiera de los Email, Contacts1, Contacts2, Contacts3, Contacts4
campos.
Lo mismo para los números de teléfono. Los usuarios pueden buscar un patrón como "70283" y una consulta debe devolver los teléfonos con estos dígitos en ellos. Incluso es para Contacts1, Contacts2, Contacts3, Contacts4
campos de formulario libre donde probablemente deberíamos eliminar todos menos los dígitos y los caracteres de espacio antes de buscar.
Solíamos usar LIKE
para la búsqueda cuando teníamos alrededor de 1500 registros y funcionó bien, pero ahora tenemos muchos registros y la LIKE
búsqueda lleva infinitos para obtener resultados.
Así es como intentamos obtener datos desde allí:
SELECT * FROM NewCompanies WHERE CONTAINS((Email, Contacts1, Contacts2, Contacts3, Contacts4), '"[email protected]*"') -- this doesn't get the row
SELECT * FROM NewCompanies WHERE CONTAINS((Phone, Contacts1, Contacts2, Contacts3, Contacts4), '"6662211*"') -- doesn't get anything
SELECT * FROM NewCompanies WHERE CONTAINS(Name, '"zimuth*"') -- doesn't get anything
nvarchar(MAX)
aquí? Nunca he oído hablar, ni he conocido a nadie cuyo nombre tenga 1 mil millones de caracteres de largo. Y, según esta respuesta , una dirección de correo electrónico no puede tener más de 254 caracteres; así que también tienes 1 billón de personajes desperdiciados allí.@gmail.com
como término de búsqueda porque el@
carácter es un separador de palabras. En otras palabras, según la versión de SQL Server que tiene, palabras en el índice para[email protected]
serán o bien (A)user
,gmail
ycom
o (B)user
,[email protected]
,gmail
ycom
. REF: Cambios de comportamiento en la búsqueda de texto completo.
.SELECT * FROM NewCompanies WHERE Id IN (SELECT ID from .... where MyOuterApply.EmailCol1 LIKE '%'+@SearchString+'%') OR Id IN (SELECT ID from .... where MyOuterApply.EmailCol2 LIKE '%'+@SearchString+'%')
Cree alrededor de cinco índices individuales en cada uno de los campos e incluya la clave principal.Respuestas:
Actualmente solicita
en contra
'Call only at weekends +7-999-666-22-11'
yen contra
'PJSC Azimuth'
hacer el trabajo como se esperaba .
Ver término del prefijo . Porque
6662211*
no es un prefijo de+7-999-666-22-11
y tampocozimuth*
es un prefijo deAzimuth
Como para
Esto probablemente se deba a los separadores de palabras, como siempre se aprende en los comentarios. Ver separadores de palabras
No creo que la búsqueda de texto completo sea aplicable para su tarea.
¿Por qué usar para FTS en exactamente las mismas tareas para las que se usa el operador LIKE? Si hubiera un mejor tipo de índice para las consultas LIKE ... entonces habría un mejor tipo de índice , no la tecnología y la sintaxis totalmente diferentes.
Y de ninguna manera te ayudará a enfrentarte
"6662211*"
contra "666 some arbitraria char 22 some arbitraria char 11".La búsqueda de texto completo no se trata de
"6662211*"
expresiones regulares (y ni siquiera es una expresión correcta para el trabajo; no hay nada sobre la parte de "algún carácter arbitrario") se trata de sinónimos, formas de palabras, etc.¿Pero es posible buscar subcadenas de manera efectiva?
Sí lo es. Dejando de lado perspectivas como escribir su propio motor de búsqueda, ¿qué podemos hacer dentro
SQL
?En primer lugar, ¡es imperativo limpiar sus datos! Si desea devolver a los usuarios las cadenas exactas que han ingresado
... puedes guardarlos como están ... y dejarlos junto.
Luego, debe extraer datos del texto de forma libre (no es tan difícil para correos electrónicos y números de teléfono) y guardar los datos en alguna forma canónica. Para el correo electrónico, lo único que realmente necesita hacer: hacerlos todos en minúsculas o mayúsculas (no importa), y tal vez dividirlos luego en el
@
canto. Pero en los números de teléfono debe dejar solo dígitos(... Y luego incluso puede almacenarlos como números . Eso puede ahorrarle algo de espacio y tiempo. Pero la búsqueda será diferente ... Por ahora vamos a sumergirnos en algo más simple y solución universal usando cadenas.)
Como MatthewBaker mencionó , puede crear una tabla de sufijos. Entonces puedes buscar así
Debe colocar el comodín
%
solo al final . O no habría beneficios de la tabla de sufijos.Tomemos por ejemplo un número de teléfono
Después de deshacernos de los caracteres de desecho, tendrá 11 dígitos. Eso significa que necesitaremos 11 sufijos para un número de teléfono
Entonces, la complejidad del espacio para esta solución es lineal ... no está tan mal, diría ... Pero espere que sea la complejidad en el número de registros. Pero en los símbolos ... necesitamos
N(N+1)/2
símbolos para almacenar todos los sufijos, eso es complejidad cuadrática ... no es bueno ... pero si ahora tiene100 000
registros y no tiene planes para millones en el futuro cercano, puede continuar con esto solución.¿Podemos reducir la complejidad del espacio?
Solo describiré la idea, implementarla requerirá un poco de esfuerzo. Y probablemente tendremos que cruzar los límites de
SQL
Digamos que tiene 2 filas
NewCompanies
y 2 cadenas de texto de forma libre:¿Qué tan grande debe ser la tabla de sufijos? Obviamente, solo necesitamos 2 registros.
Tomemos otro ejemplo. También 2 filas, 2 cadenas de texto libre para buscar. Pero ahora es:
Veamos cuántos sufijos necesitamos ahora:
No tan mal, pero tampoco tan bueno.
qué más podemos hacer?
Digamos que el usuario ingresa
"c11"
en el campo de búsqueda. LuegoLIKE 'c11%'
necesita el sufijo ' c11 cc' para tener éxito. Pero si en lugar de buscar"c11"
, primero buscamos"c%"
, ¿luego,"c1%"
etc.? La primera búsqueda dará como solo una fila desdeNewCompanies
. Y no habría necesidad de búsquedas posteriores. Y podemosy terminamos con solo 4 sufijos
No puedo decir cuál sería la complejidad del espacio en este caso, pero parece que sería aceptable.
fuente
En casos como este, la búsqueda de texto completo es menos que ideal. Estaba en el mismo bote que tú. Las búsquedas similares son demasiado lentas, y las búsquedas de texto completo buscan palabras que comienzan con un término en lugar de contener un término.
Probamos varias soluciones, una opción de SQL puro es crear su propia versión de búsqueda de texto completo, en particular una búsqueda de índice invertido. Intentamos esto, y fue exitoso, pero tomó mucho espacio. Creamos una tabla de retención secundaria para términos de búsqueda parcial, y utilizamos indexación de texto completo en eso. Sin embargo, esto significa que almacenamos repetidamente varias copias de la misma cosa. Por ejemplo, almacenamos "longword" como Longword, ongword, ngword, gword ... etc. Por lo tanto, cualquier frase contenida siempre estará al comienzo del término indexado. Una solución horrible, llena de defectos, pero funcionó.
Luego buscamos alojar un servidor separado para las búsquedas. Buscar en Google Lucene y elastisearch le dará buena información sobre estos paquetes disponibles.
Finalmente, desarrollamos nuestro propio motor de búsqueda interno, que se ejecuta junto con SQL. Esto nos ha permitido implementar búsquedas fonéticas (doble metafonía) y luego usar cálculos de levenshtein junto con el índice de sonido para establecer relevancia. Exagerar para muchas soluciones, pero vale la pena el esfuerzo en nuestro caso de uso. Incluso ahora tenemos la opción de aprovechar las GPU Nvidia para las búsquedas de cuda, pero esto representó un nuevo conjunto de dolores de cabeza y noches de insomnio. La relevancia de todo esto dependerá de la frecuencia con la que veas que se realizan tus búsquedas y de cuán reactivo necesitas que sean.
fuente
Los índices de texto completo tienen varias limitaciones. Puede usar comodines en palabras que el índice encuentra como "partes" enteras, pero aun así está limitado a la parte final de la palabra. Es por eso que puedes usar
CONTAINS(Name, '"Azimut*"')
pero noCONTAINS(Name, '"zimuth*"')
De la documentación de Microsoft :
Los puntos en el correo electrónico, como lo indica el título, no son el problema principal. Esto, por ejemplo, funciona:
En este caso, el índice identifica toda la cadena de correo electrónico como válida, así como "gmail" y "gmail.com". Solo "sms" aunque no es válido.
El último ejemplo es similar. Las partes del número de teléfono están indexadas (666-22-11 y 999-666-22-11, por ejemplo), pero eliminar los guiones no es una cadena que el índice va a conocer. De lo contrario, esto funciona:
fuente