MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Combinar texto completo com outro índice


O caso principal aqui é que um resultado de pesquisa de "texto" geralmente tem precedência sobre outras condições de filtro na consulta e, como tal, torna-se necessário "primeiro" obter resultados do componente "texto" e depois basicamente "varrer" para outras condições do documento.

Esse tipo de pesquisa pode ser difícil de otimizar junto com um "intervalo" ou qualquer tipo de condição de correspondência de "desigualdade" em conjunto com os resultados da pesquisa de texto, e é principalmente devido à forma como o MongoDB lida com esse tipo de índice "especial".

Para uma breve demonstração, considere a seguinte configuração básica:
db.texty.drop();

db.texty.insert([
    { "a": "a", "text": "something" },
    { "a": "b", "text": "something" },
    { "a": "b", "text": "nothing much" },
    { "a": "c", "text": "something" }
])

db.texty.createIndex({ "text": "text" })
db.texty.createIndex({ "a": 1 })

Então, se você quiser ver isso com uma condição de pesquisa de texto, bem como uma consideração de intervalo no outro campo ( { "$lt": "c" } ), então você pode lidar da seguinte forma:
db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } }).explain()

Com a saída de explicação, como (parte importante):
           "winningPlan" : {
                    "stage" : "FETCH",
                    "filter" : {
                            "a" : {
                                    "$lt" : "c"
                            }
                    },
                    "inputStage" : {
                            "stage" : "TEXT",
                            "indexPrefix" : {

                            },
                            "indexName" : "text_text",
                            "parsedTextQuery" : {
                                    "terms" : [
                                            "someth"
                                    ],
                                    "negatedTerms" : [ ],
                                    "phrases" : [ ],
                                    "negatedPhrases" : [ ]
                            },
                            "inputStage" : {
                                    "stage" : "TEXT_MATCH",
                                    "inputStage" : {
                                            "stage" : "TEXT_OR",
                                            "inputStage" : {
                                                    "stage" : "IXSCAN",
                                                    "keyPattern" : {
                                                            "_fts" : "text",
                                                            "_ftsx" : 1
                                                    },
                                                    "indexName" : "text_text",
                                                    "isMultiKey" : true,
                                                    "isUnique" : false,
                                                    "isSparse" : false,
                                                    "isPartial" : false,
                                                    "indexVersion" : 1,
                                                    "direction" : "backward",
                                                    "indexBounds" : {

                                                    }
                                            }
                                    }
                            }
                    }
            },

O que basicamente significa "primeiro obtenha os resultados de texto e, em seguida, filtre os resultados obtidos pela outra condição" . Então, claramente, apenas o índice "texto" está sendo usado aqui e, em seguida, todos os resultados que ele retorna são posteriormente filtrados examinando o conteúdo.

Isso não é ideal por dois motivos, sendo que provavelmente os dados são mais restritos pela condição de "intervalo" do que pelas correspondências da pesquisa de texto. Em segundo lugar, embora haja um índice nos outros dados, ele não está sendo usado aqui para comparação. Assim, todo o documento é carregado para cada resultado e o filtro é testado.

Você pode então considerar um formato de índice "composto" aqui, e parece inicialmente lógico que, se o "intervalo" for mais específico para seleção, inclua isso como a ordem prefixada das chaves indexadas:
db.texty.dropIndexes();
db.texty.createIndex({ "a": 1, "text": "text" })

Mas há um problema aqui, pois quando você tenta executar a consulta novamente:
db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } })

Isso resultaria em um erro:

Erro:erro:{"waitedMS" :NumberLong(0),"ok" :0,"errmsg" :"erro ao processar consulta:ns=test.textyTree:$and\n a $lt \"c\"\n TEXT :query=something, language=english, caseSensitive=0, diacriticSensitive=0, tag=NULL\nSort:{}\nProj:{}\n planejador retornou erro:falha ao usar o índice de texto para satisfazer a consulta $text (se o índice de texto for composto, os predicados de igualdade são fornecidos para todos os campos de prefixo?)","code" :2}

Portanto, mesmo que isso possa parecer "ótimo", a maneira como o MongoDB processa a consulta (e realmente a seleção de índice) para o índice "texto" especial, simplesmente não é possível que essa "exclusão" fora do intervalo seja possível.

No entanto, você pode executar uma correspondência de "igualdade" de maneira muito eficiente:
db.texty.find({ "a": "b", "$text": { "$search": "something" } }).explain()

Com a saída de explicação:
           "winningPlan" : {
                    "stage" : "TEXT",
                    "indexPrefix" : {
                            "a" : "b"
                    },
                    "indexName" : "a_1_text_text",
                    "parsedTextQuery" : {
                            "terms" : [
                                    "someth"
                            ],
                            "negatedTerms" : [ ],
                            "phrases" : [ ],
                            "negatedPhrases" : [ ]
                    },
                    "inputStage" : {
                            "stage" : "TEXT_MATCH",
                            "inputStage" : {
                                    "stage" : "TEXT_OR",
                                    "inputStage" : {
                                            "stage" : "IXSCAN",
                                            "keyPattern" : {
                                                    "a" : 1,
                                                    "_fts" : "text",
                                                    "_ftsx" : 1
                                            },
                                            "indexName" : "a_1_text_text",
                                            "isMultiKey" : true,
                                            "isUnique" : false,
                                            "isSparse" : false,
                                            "isPartial" : false,
                                            "indexVersion" : 1,
                                            "direction" : "backward",
                                            "indexBounds" : {

                                            }
                                    }
                            }
                    }
            },

Assim, o índice é usado e pode ser mostrado para "pré-filtrar" o conteúdo fornecido ao texto correspondente pela saída da outra condição.

Se, de fato, você mantiver o "prefixo" do índice como o(s) campo(s) de "texto" para pesquisar, no entanto:
db.texty.dropIndexes();

db.texty.createIndex({ "text": "text", "a": 1 })

Em seguida, faça a pesquisa:
db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } }).explain()

Então você vê um resultado semelhante à correspondência de "igualdade" acima:
            "winningPlan" : {
                    "stage" : "TEXT",
                    "indexPrefix" : {

                    },
                    "indexName" : "text_text_a_1",
                    "parsedTextQuery" : {
                            "terms" : [
                                    "someth"
                            ],
                            "negatedTerms" : [ ],
                            "phrases" : [ ],
                            "negatedPhrases" : [ ]
                    },
                    "inputStage" : {
                            "stage" : "TEXT_MATCH",
                            "inputStage" : {
                                    "stage" : "TEXT_OR",
                                    "filter" : {
                                            "a" : {
                                                    "$lt" : "c"
                                            }
                                    },
                                    "inputStage" : {
                                            "stage" : "IXSCAN",
                                            "keyPattern" : {
                                                    "_fts" : "text",
                                                    "_ftsx" : 1,
                                                    "a" : 1
                                            },
                                            "indexName" : "text_text_a_1",
                                            "isMultiKey" : true,
                                            "isUnique" : false,
                                            "isSparse" : false,
                                            "isPartial" : false,
                                            "indexVersion" : 1,
                                            "direction" : "backward",
                                            "indexBounds" : {

                                            }
                                    }
                            }
                    }
            },

A grande diferença aqui da primeira tentativa é onde filter é colocado na cadeia de processamento, indicando que, embora não seja uma correspondência de "prefixo" (o que é mais ideal), o conteúdo está de fato sendo verificado do índice "antes" de ser enviado para o estágio de "texto".

Portanto, é "pré-filtrado", mas não da maneira mais ideal, é claro, e isso se deve à própria natureza de como o índice "texto" é usado. Então, se você acabou de considerar o intervalo simples em um índice por si só:
db.texty.createIndex({ "a": 1 })
db.texty.find({ "a": { "$lt": "c" } }).explain()

Em seguida, a saída de explicação:
            "winningPlan" : {
                    "stage" : "FETCH",
                    "inputStage" : {
                            "stage" : "IXSCAN",
                            "keyPattern" : {
                                    "a" : 1
                            },
                            "indexName" : "a_1",
                            "isMultiKey" : false,
                            "isUnique" : false,
                            "isSparse" : false,
                            "isPartial" : false,
                            "indexVersion" : 1,
                            "direction" : "forward",
                            "indexBounds" : {
                                    "a" : [
                                            "[\"\", \"c\")"
                                    ]
                            }
                    }
            },

Então isso pelo menos tem o indexBounds considerar e apenas olhou para a parte do índice que estava dentro desses limites.

Então essas são as diferenças aqui. Usar uma estrutura "composta" deve economizar alguns ciclos de iteração aqui, pois pode restringir a seleção, mas ainda deve verificar todas as entradas de índice para filtrar e, é claro, não ser o elemento "prefixo" no índice, a menos que você possa usar uma correspondência de igualdade nele.

Sem uma estrutura composta no índice, você está sempre retornando os resultados de texto "primeiro" e, em seguida, aplicando quaisquer outras condições a esses resultados. Também não é possível "combinar/interseccionar" os resultados de olhar para um índice de "texto" e um índice "normal" devido ao manuseio do mecanismo de consulta. Essa geralmente não será a abordagem ideal, portanto, é importante planejar as considerações.

Em suma, idealmente composto com um "prefixo" de correspondência de "igualdade" e, caso contrário, inclua no índice "após" a definição do texto.