Un RoSe en cualquier otro caso olería igual de dulce. William Shakespeare
Debes haber aprendido normas de capitalización en su escuela de gramática, pero la búsqueda en el mundo real no es tan sensible a las mayúsculas. Charles de Gaulle utiliza minúsculas para la "de" intermedia, Tony La Russa utiliza mayúsculas para "La" - puede haber razones etimológicas para ello, pero es poco probable que su agente de servicio al cliente lo recuerde. Las bases de datos tienen varias sensibilidades. SQL, por defecto, no distingue entre mayúsculas y minúsculas en los identificadores y palabras clave, pero sí en los datos. JSON distingue entre mayúsculas y minúsculas tanto en los nombres de campo como en los datos. Lo mismo ocurre con N1QL. JSON puede tener lo siguiente. N1QL select-join-proyectará cada campo y valor como un campo y valor distintos.
|
1 2 3 4 5 6 7 8 9 10 11 |
SELECT {"City": "San Francisco", "city": "san francisco", "citY": "saN fanciscO"} [ { "$1": { "City": "San Francisco", "citY": "saN fanciscO", "city": "san francisco" } } ] |
En este artículo hablaremos de cómo hacer frente a distinción entre mayúsculas y minúsculas en los datos. Sus referencias de campo siguen siendo distingue entre mayúsculas y minúsculas. Si usa el caso incorrecto para el nombre del campo, N1QL asume que este es un campo faltante y asigna el valor FALTANTE a ese campo.
Consideremos un predicado simple en N1QL para buscar todas las permutaciones de casos.
|
1 |
WHERE name in [“joe”, “joE”, “jOe”, “Joe”, “JoE”, “JOe”, “JOE”] |
Esto requiere siete búsquedas diferentes en el índice. "John" requiere más búsquedas en el índice y "Fitzerald" aún más. Existe una forma estándar de hacerlo. Basta con crear un índice bajando el caso del campo y el literal.
|
1 |
WHERE LOWER(name) = “joe” |
Esta búsqueda puede hacerse más rápida creando el índice con la expresión correcta.
|
1 |
CREATE INDEX i1 ON customer(LOWER(name)); |
Asegúrese de que su consulta está recogiendo el índice correcto y empuja el predicado a la exploración del índice. Y esa es la idea. Las consultas que tienen predicados empujados a la exploración del índice se ejecutan mucho más rápido que las consultas que no. Esto es cierto para los predicados y cierto empuje agregado también.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
EXPLAIN SELECT * FROM `customer` WHERE LOWER(name) = "joe"; { "#operator": "IndexScan3", "index": "i1", "index_id": "c117bdf583c2e276", "index_projection": { "primary_key": true }, "keyspace": "customer", "namespace": "default", "spans": [ { "exact": true, "range": [ { "high": "\"joe\"", "inclusion": 3, "low": "\"joe\"" } ] } ], |
Insensibilidad a los casos en un escenario de índice compuesto.
|
1 2 3 4 5 |
WHERE LOWER(name) = “joe” AND zip = 94821 AND salary > 500 AND join_date <= “2017-01-01” AND LOWER(county) LIKE “san%” |
|
1 2 3 4 5 |
CREATE INDEX i2 ON customer(LOWER(name), zip, LOWER(county), join_date, salary) |
Insensibilidad a mayúsculas y minúsculas en funciones Array.
Funciones de cadena como SPLIT(), SUFFIXES(), muchas de las funciones de matriz y funciones del objeto sí devuelven matrices. Entonces, ¿cómo utilizarlos sin distinguir entre mayúsculas y minúsculas?
Seguimos el mismo principio que antes. Crea primero una expresión para minusvalorar los valores antes de procesarlos mediante estas funciones.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
SELECT SPLIT("Good Morning, Joe") as splitresult; "splitresult": [ "Good", "Morning,", "Joe" ] SELECT SPLIT(LOWER(“Good Morning, Joe”)); "splitresult": [ "good", "morning,", "joe" ] |
Ahora, lo que realmente quieres es filtrar en base a un valor dentro de la cadena.
|
1 |
WHERE LOWER(xyz) LIKE “%good%”; |
Este es probablemente el peor predicado en SQL - en términos de rendimiento.
|
1 2 |
SELECT * FROM customer WHERE x IN SPLIT(LOWER(xyz)) SATISFIES x = “good” END |
Ahora, ¿qué índice crearías para esto? CONSEJO es muy útil.
|
1 2 |
CREATE INDEX adv_DISTINCT_split_lower_xyz ON `customer` (DISTINCT ARRAY `x` FOR x in split(lower((`xyz`))) END) |
Como de costumbre, verifique su explicación.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "#operator": "DistinctScan", "scan": { "#operator": "IndexScan3", "index": "adv_DISTINCT_split_lower_xyz", "index_id": "552ab6c643616fbc", "index_projection": { "primary_key": true }, "keyspace": "customer", "namespace": "default", "spans": [ { "exact": true, "range": [ { "high": "\"good\"", "inclusion": 3, "low": "\"good\"" } ] } ], |
Si desea utilizar UNNEST y una cláusula WHERE sencilla, utilice esta consulta. Verifique siempre su explicación para asegurarse de que los predicados son empujados a la exploración del índice.
|
1 2 3 |
SELECT * FROM customer UNNEST SPLIT(LOWER(xyz)) AS x WHERE x = "good" |
Uso de fichas
La función TOKENS() simplifica la obtención de las minúsculas tomando esa opción como argumento. Véase el artículo Más que LIKE: Búsqueda eficiente en JSON con N1QL para más detalles y ejemplos
Expresiones complejas.
|
1 2 3 4 |
SELECT * FROM customer WHERE lower(fname) || lower(mname) || lower(lname) = “JoeMSmith” |
¿Cómo podemos optimizarlo? Index Advisor al rescate. Otra vez.
|
1 2 |
CREATE INDEX adv_lower_fname_concat_lower_mname_concat_lower_lname ON `customer`(lower((`fname`))||lower((`mname`))||lower((`lname`))) |
Explicar para confirmar el plan:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "#operator": "IndexScan3", "index": "adv_lower_fname_concat_lower_mname_concat_lower_lname", "index_id": "aaa14cbdf14e9cd8", "index_projection": { "primary_key": true }, "keyspace": "customer", "namespace": "default", "spans": [ { "exact": true, "range": [ { "high": "\"JoeMSmith\"", "inclusion": 3, "low": "\"JoeMSmith\"" } ] } ], "using": "gsi" }, |
Búsqueda de textos completos
Como te habrás dado cuenta, se trata de un problema de procesamiento y consulta de texto. El FTS puede escanear, almacenar y buscar texto de varias maneras. La búsqueda insensible a mayúsculas y minúsculas es una de ellas. Veamos el plan para una simple consulta de búsqueda.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
select * from customer where search (name, "joe") "~children": [ { "#operator": "PrimaryScan3", "index": "#primary", "index_projection": { "primary_key": true }, "keyspace": "customer", "namespace": "default", "using": "gsi" }, { "#operator": "Fetch", "keyspace": "customer", "namespace": "default" }, { "#operator": "Parallel", "~child": { "#operator": "Sequence", "~children": [ { "#operator": "Filter", "condition": "search((`customer`.`name`), \"joe\")" }, { "#operator": "InitialProject", "result_terms": [ { "expr": "self", "star": true } ] } ] } } ] } |
Este NO es el plan que quieres... ¡Esto es usar un escáner primario!
Después de crear el índice de texto en el cliente cubo, las cosas van mucho mejor:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
select * from customer where search (name, "joe") { "#operator": "Sequence", "~children": [ { "#operator": "IndexFtsSearch", "index": "trname", "index_id": "3bdb61e5010e8838", "keyspace": "customer", "namespace": "default", "search_info": { "field": "\"`name`\"", "outname": "out", "query": "\"joe\"" }, "using": "fts" }, |
El analizador estándar por defecto baja todos los tokens y por lo tanto encontrará todos los "joe "s : JOE, joe, Joe, JOe, etc. Puede definir un analizador personalizado y proporcionar instrucciones específicas para minusvalorar los tokens. He aquí un ejemplo.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
"mapping": { "analysis": { "analyzers": { "mylower": { "token_filters": [ "to_lower" ], "tokenizer": "unicode", "type": "custom" } } }, |
Así es como se añade en la interfaz de usuario. Ver blog fino 8 formas de personalizar los índices de búsqueda de texto completo de Couchbase para más detalles sobre las distintas formas de personalizar el índice FTS.