Pesquisar dados indexados
Pesquisar dados indexados no BigQuery permite
melhorar o desempenho da consulta e minimizar a quantidade de dados verificados.
Use a função SEARCH
e os operadores compatíveis nas tabelas
indexadas para maximizar a economia em
bytes processados
e milissegundos de slot,
especialmente ao recuperar uma pequena fração do total de linhas.
Criar um índice de pesquisa.
A tabela a seguir, chamada Logs, é usada para mostrar maneiras diferentes de usar a função SEARCH. Esta tabela de exemplo é bem pequena, mas, na
prática, os ganhos de desempenho que você consegue com SEARCH melhoram com o tamanho da tabela.
CREATE TABLE my_dataset.Logs (Level STRING, Source STRING, Message STRING) AS ( SELECT 'INFO' as Level, '65.177.8.234' as Source, 'Entry Foo-Bar created' as Message UNION ALL SELECT 'WARNING', '132.249.240.10', 'Entry Foo-Bar already exists, created by 65.177.8.234' UNION ALL SELECT 'INFO', '94.60.64.181', 'Entry Foo-Bar deleted' UNION ALL SELECT 'SEVERE', '4.113.82.10', 'Entry Foo-Bar does not exist, deleted by 94.60.64.181' UNION ALL SELECT 'INFO', '181.94.60.64', 'Entry Foo-Baz created' );
A tabela tem esta aparência:
+---------+----------------+-------------------------------------------------------+ | Level | Source | Message | +---------+----------------+-------------------------------------------------------+ | INFO | 65.177.8.234 | Entry Foo-Bar created | | WARNING | 132.249.240.10 | Entry Foo-Bar already exists, created by 65.177.8.234 | | INFO | 94.60.64.181 | Entry Foo-Bar deleted | | SEVERE | 4.113.82.10 | Entry Foo-Bar does not exist, deleted by 94.60.64.181 | | INFO | 181.94.60.64 | Entry Foo-Baz created | +---------+----------------+-------------------------------------------------------+
Crie um índice de pesquisa na tabela Logs usando o analisador de texto padrão:
CREATE SEARCH INDEX my_index ON my_dataset.Logs(ALL COLUMNS);
Saiba mais sobre índices de pesquisa em Gerenciar índices de pesquisa.
Usar a função SEARCH
A função SEARCH faz uma pesquisa tokenizada nos dados.
SEARCH foi projetado para ser usado com um índice para
otimizar as pesquisas.
Use a função SEARCH para pesquisar uma tabela inteira ou restringir sua pesquisa a colunas específicas.
Pesquisar uma tabela inteira
A consulta a seguir pesquisa o valor bar em todas as colunas
da tabela Logs e retorna as linhas que contêm esse valor, independentemente do
uso de letras maiúsculas. Como o índice de pesquisa usa o analisador de texto padrão, não
é necessário especificá-lo na função SEARCH.
SELECT * FROM my_dataset.Logs WHERE SEARCH(Logs, 'bar');
+---------+----------------+-------------------------------------------------------+ | Level | Source | Message | +---------+----------------+-------------------------------------------------------+ | INFO | 65.177.8.234 | Entry Foo-Bar created | | WARNING | 132.249.240.10 | Entry Foo-Bar already exists, created by 65.177.8.234 | | INFO | 94.60.64.181 | Entry Foo-Bar deleted | | SEVERE | 4.113.82.10 | Entry Foo-Bar does not exist, deleted by 94.60.64.181 | +---------+----------------+-------------------------------------------------------+
A consulta a seguir procura o valor `94.60.64.181` em todas as colunas da tabela Logs e retorna as linhas que contêm esse valor. Os
acentos graves permitem uma pesquisa exata. Por isso, a última linha da tabela
Logs que contém 181.94.60.64 é omitida.
SELECT * FROM my_dataset.Logs WHERE SEARCH(Logs, '`94.60.64.181`');
+---------+----------------+-------------------------------------------------------+ | Level | Source | Message | +---------+----------------+-------------------------------------------------------+ | INFO | 94.60.64.181 | Entry Foo-Bar deleted | | SEVERE | 4.113.82.10 | Entry Foo-Bar does not exist, deleted by 94.60.64.181 | +---------+----------------+-------------------------------------------------------+
Pesquisar um subconjunto de colunas
SEARCH facilita a especificação de um subconjunto de colunas para a pesquisa de
dados. A consulta a seguir pesquisa a coluna Message da tabela Logs em busca do
valor 94.60.64.181 e retorna as linhas que contêm esse valor.
SELECT * FROM my_dataset.Logs WHERE SEARCH(Message, '`94.60.64.181`');
+---------+----------------+-------------------------------------------------------+ | Level | Source | Message | +---------+----------------+-------------------------------------------------------+ | SEVERE | 4.113.82.10 | Entry Foo-Bar does not exist, deleted by 94.60.64.181 | +---------+----------------+-------------------------------------------------------+
A consulta a seguir pesquisa as colunas Source e Message da
tabela Logs. Ele retorna as linhas que contêm o valor 94.60.64.181 de
qualquer uma das colunas.
SELECT * FROM my_dataset.Logs WHERE SEARCH((Source, Message), '`94.60.64.181`');
+---------+----------------+-------------------------------------------------------+ | Level | Source | Message | +---------+----------------+-------------------------------------------------------+ | INFO | 94.60.64.181 | Entry Foo-Bar deleted | | SEVERE | 4.113.82.10 | Entry Foo-Bar does not exist, deleted by 94.60.64.181 | +---------+----------------+-------------------------------------------------------+
Excluir colunas de uma pesquisa
Se uma tabela tiver muitas colunas e você quiser pesquisar a maioria delas, talvez seja
mais fácil especificar apenas as colunas a serem excluídas da pesquisa. A consulta
a seguir pesquisa em todas as colunas da tabela Logs, exceto
na coluna Message. Ela retorna as linhas de qualquer coluna diferente de Message
que contenha o valor 94.60.64.181.
SELECT * FROM my_dataset.Logs WHERE SEARCH( (SELECT AS STRUCT Logs.* EXCEPT (Message)), '`94.60.64.181`');
+---------+----------------+---------------------------------------------------+ | Level | Source | Message | +---------+----------------+---------------------------------------------------+ | INFO | 94.60.64.181 | Entry Foo-Bar deleted | +---------+----------------+---------------------------------------------------+
Usar um analisador de texto diferente
O exemplo a seguir cria uma tabela chamada contact_info com um índice que
usa o analisador de texto
NO_OP_ANALYZER:
CREATE TABLE my_dataset.contact_info (name STRING, email STRING) AS ( SELECT 'Kim Lee' AS name, 'kim.lee@example.com' AS email UNION ALL SELECT 'Kim' AS name, 'kim@example.com' AS email UNION ALL SELECT 'Sasha' AS name, 'sasha@example.com' AS email ); CREATE SEARCH INDEX noop_index ON my_dataset.contact_info(ALL COLUMNS) OPTIONS (analyzer = 'NO_OP_ANALYZER');
+---------+---------------------+ | name | email | +---------+---------------------+ | Kim Lee | kim.lee@example.com | | Kim | kim@example.com | | Sasha | sasha@example.com | +---------+---------------------+
A consulta a seguir pesquisa Kim na coluna name e kim
na coluna email.
Como o índice de pesquisa não usa o analisador de texto padrão, é necessário transmitir o
nome do analisador para a função SEARCH.
SELECT name, SEARCH(name, 'Kim', analyzer=>'NO_OP_ANALYZER') AS name_Kim, email, SEARCH(email, 'kim', analyzer=>'NO_OP_ANALYZER') AS email_kim FROM my_dataset.contact_info;
O NO_OP_ANALYZER não modifica o texto. Portanto, a função SEARCH retorna
apenas TRUE para correspondências exatas:
+---------+----------+---------------------+-----------+ | name | name_Kim | email | email_kim | +---------+----------+---------------------+-----------+ | Kim Lee | FALSE | kim.lee@example.com | FALSE | | Kim | TRUE | kim@example.com | FALSE | | Sasha | FALSE | sasha@example.com | FALSE | +---------+----------+---------------------+-----------+
Configurar opções do analisador de texto
Os analisadores de
texto LOG_ANALYZER e PATTERN_ANALYZER podem ser
personalizados adicionando uma string formatada em JSON às opções de configuração. É possível
configurar analisadores de texto na função
SEARCH, na instrução DDL
CREATE
SEARCH INDEX
e na TEXT_ANALYZE.
O exemplo a seguir cria uma tabela chamada complex_table com um índice que
usa o analisador de texto
LOG_ANALYZER: Ele usa uma string formatada em JSON para
configurar as opções do analisador:
CREATE TABLE dataset.complex_table( a STRING, my_struct STRUCT<string_field STRING, int_field INT64>, b ARRAY<STRING> ); CREATE SEARCH INDEX my_index ON dataset.complex_table(a, my_struct, b) OPTIONS (analyzer = 'LOG_ANALYZER', analyzer_options = '''{ "token_filters": [ { "normalizer": {"mode": "NONE"} } ] }''');
A tabela a seguir mostra exemplos de chamadas para a função SEARCH com
diferentes analisadores de texto e os respectivos resultados. A primeira tabela chama a função SEARCH
usando o analisador de texto padrão, o LOG_ANALYZER:
| Chamada de função | Retorna | Motivo |
|---|---|---|
| SEARCH('foobarexample', NULL) | ERRO | O search_terms é `NULL`. |
| SEARCH('foobarexample', '') | ERRO | O "search_terms" não contém tokens. |
| SEARCH('foobar-example', 'foobar example') | VERDADEIRO | "-" e " " são delimitadores. |
| SEARCH('foobar-example', 'foobarexample') | FALSO | Os search_terms não são divididos. |
| SEARCH('foobar-exemplo', 'foobar\\&example') | VERDADEIRO | A barra invertida dupla faz o escape do E comercial, que é um delimitador. |
| SEARCH('foobar-exemplo', R'foobar\&example') | VERDADEIRO | Essa barra invertida faz o escape do "e" comercial em uma string bruta. |
| SEARCH('foobar-exemplo', '`foobar&example`') | FALSO | Os acentos graves exigem uma correspondência exata para foobar&example. |
| SEARCH('foobar&example', '`foobar&example`') | VERDADEIRO | Uma correspondência exata é encontrada. |
| SEARCH('foobar-example', 'exemplo foobar') | VERDADEIRO | A ordem dosprefixos não importa. |
| SEARCH('foobar-example', 'foobar example') | VERDADEIRO | Os tokens ficam em letras minúsculas. |
| SEARCH('foobar-example', '`foobar-example`') | VERDADEIRO | Uma correspondência exata é encontrada. |
| SEARCH('foobar-example', '`foobar`') | FALSO | Acentos graves preservam as letras maiúsculas. |
| SEARCH('`foobar-example`', '`foobar-example`') | FALSO | Acentos graves não têm significado especial para data_to_search e |
| SEARCH('foobar@exemplo.com', '`exemplo.com`') | VERDADEIRO | É encontrada uma correspondência exata após o delimitador em data_to_search. |
| SEARCH('a foobar-example b', '`foobar-example`') | VERDADEIRO | Uma correspondência exata é encontrada entre os delimitadores de espaço. |
| SEARCH(['foobar', 'example'], 'foobar example') | FALSO | Nenhuma entrada de matriz corresponde a todos os termos de pesquisa. |
| SEARCH('foobar=', '`foobar\\=`') | FALSO | Search_terms é equivalente a foobar\=. |
| SEARCH('foobar=', R'`foobar\=`') | FALSO | Isso é equivalente ao exemplo anterior. |
| SEARCH('foobar=', 'foobar\\=') | VERDADEIRO | O sinal de igual é um delimitador nos dados e na consulta. |
| SEARCH('foobar=', R'foobar\=') | VERDADEIRO | Isso é equivalente ao exemplo anterior. |
| SEARCH('foobar.example', '`foobar`') | VERDADEIRO | Uma correspondência exata é encontrada. |
| SEARCH('foobar.example', '`foobar.`') | FALSO | `foobar.` não é analisado devido a acentos graves. não está |
| SEARCH('foobar..example', '`foobar.`') | VERDADEIRO | `foobar.` não é analisado devido a acentos graves. é seguido |
A tabela a seguir mostra exemplos de chamadas para a função SEARCH usando o
analisador de texto NO_OP_ANALYZER e os motivos de vários valores de retorno:
| Chamada de função | Retorna | Motivo |
|---|---|---|
| SEARCH('foobar', 'foobar', Analyzer=>'NO_OP_ANALYZER') | VERDADEIRO | Uma correspondência exata é encontrada. |
| SEARCH('foobar', '`foobar`', Analyzer=>'NO_OP_ANALYZER') | FALSO | Acentos graves não são caracteres especiais para NO_OP_ANALYZER. |
| SEARCH('Foobar', 'foobar', analyzer=>'NO_OP_ANALYZER') | FALSO | As letras maiúsculas não correspondem. |
| SEARCH('exemplo de foobar', 'foobar', analyzer=>'NO_OP_ANALYZER') | FALSO | Não há delimitadores para NO_OP_ANALYZER. |
| SEARCH('', '', Analyzer=>'NO_OP_ANALYZER') | VERDADEIRO | Não há delimitadores para NO_OP_ANALYZER. |
Outros operadores e funções
É possível realizar otimizações de índice de pesquisa com vários operadores, funções e predicados.
Otimizar com operadores e funções de comparação
O BigQuery pode otimizar algumas consultas que usam o
operador igual
(=),
o operador IN,
o operador LIKE,
a função STARTS_WITH
ou a
função ENDS_WITH
para comparar literais de string com dados indexados.
Otimizar com predicados de string
Os seguintes predicados estão qualificados para otimização do índice de pesquisa:
column_name = 'string_literal''string_literal' = column_namestruct_column.nested_field = 'string_literal'string_array_column[OFFSET(0)] = 'string_literal'string_array_column[ORDINAL(1)] = 'string_literal'column_name IN ('string_literal1', 'string_literal2', ...)column_name LIKE 'prefix%'STARTS_WITH(column_name, 'prefix')ENDS_WITH(column_name, 'some suffix')
Otimizar com predicados numéricos
Se o índice de pesquisa tiver sido criado com tipos de dados numéricos, o BigQuery
poderá otimizar algumas consultas que usam o operador de igualdade (=) ou o operador IN
com dados indexados. Os seguintes predicados estão qualificados para otimização do índice de pesquisa:
INT64(json_column.int64_field) = 1int64_column = 1int64_array_column[OFFSET(0)] = 1int64_column IN (1, 2)struct_column.nested_int64_field = 1struct_column.nested_timestamp_field = TIMESTAMP "2024-02-15 21:31:40"timestamp_column = "2024-02-15 21:31:40"timestamp_column IN ("2024-02-15 21:31:40", "2024-02-16 21:31:40")
Otimizar funções que produzem dados indexados
O BigQuery oferece suporte à otimização do índice de pesquisa quando determinadas funções são aplicadas a dados indexados.
Se o índice de pesquisa usar o analisador de texto LOG_ANALYZER padrão, será possível
aplicar as funções
UPPER
ou LOWER à coluna, como UPPER(column_name) = 'STRING_LITERAL'.
Nos dados de string JSON escalar extraídos de uma coluna JSON indexada, é possível
aplicar a função STRING
ou a versão segura dela,
SAFE.STRING.
Se o valor JSON extraído não for uma string, a função STRING
vai produzir um erro e a função SAFE.STRING retornará NULL.
Para dados STRING (e não JSON) formatados em JSON indexados, é possível aplicar as seguintes funções:
Por exemplo, suponha que você tenha a seguinte tabela indexada chamada
dataset.person_data com uma coluna JSON e uma STRING:
+----------------------------------------------------------------+-----------------------------------------+
| json_column | string_column |
+----------------------------------------------------------------+-----------------------------------------+
| { "name" : "Ariel", "email" : "cloudysanfrancisco@gmail.com" } | { "name" : "Ariel", "job" : "doctor" } |
+----------------------------------------------------------------+-----------------------------------------+
As seguintes consultas estão qualificadas para otimização:
SELECT * FROM dataset.person_data WHERE SAFE.STRING(json_column.email) = 'cloudysanfrancisco@gmail.com';
SELECT * FROM dataset.person_data WHERE JSON_VALUE(string_column, '$.job') IN ('doctor', 'lawyer', 'teacher');
As combinações dessas funções também são otimizadas, como
UPPER(JSON_VALUE(json_string_expression)) = 'FOO'.
Otimizar com JSON_FLATTEN
O BigQuery oferece suporte à otimização do índice da Pesquisa para consultas que usam a função JSON_FLATTEN para nivelar matrizes JSON, normalmente em conjunto com EXISTS e UNNEST.
Por exemplo, suponha que você tenha uma tabela chamada dataset.logs com uma coluna JSON json_payload. A consulta a seguir está qualificada para otimização:
SELECT json_payload FROM dataset.logs WHERE EXISTS( SELECT 1 FROM UNNEST(JSON_FLATTEN(JSON_QUERY(json_payload, "lax recursive $.message"))) AS f WHERE SEARCH(f, "nullpointerexception") );
Uso do índice de pesquisa
Para determinar se um índice da Pesquisa foi usado para uma consulta, confira os
detalhes do job ou consulte uma das visualizações INFORMATION_SCHEMA.JOBS*.
Ver detalhes do job
Em Informações do job dos Resultados da consulta, os campos Modo de uso do índice e Motivos não usados do índice fornecem informações detalhadas sobre uso do índice da Pesquisa.
As informações sobre o uso do índice de pesquisa também estão disponíveis no
campo searchStatistics
no método da API Jobs.Get. O
campo indexUsageMode em searchStatistics indica se um índice de pesquisa
foi usado com os seguintes valores:
UNUSED: nenhum índice de pesquisa foi usado.PARTIALLY_USED: parte da consulta usou índices de pesquisa e parte não.FULLY_USED: cada funçãoSEARCHna consulta usou um índice de pesquisa.
Quando indexUsageMode é UNUSED ou PARTIALLY_USED, o campo indexUnusedReasons
contém informações sobre por que os índices de pesquisa não foram usados na consulta.
Para ver searchStatistics de uma consulta, execute o comando bq show.
bq show --format=prettyjson -j JOB_ID
Exemplo
Imagine que você execute uma consulta que chama a função SEARCH nos dados de uma tabela. É possível visualizar os detalhes do job da consulta para encontrar o ID da tarefa e, em seguida, executar o comando bq show para ver mais informações:
bq show --format=prettyjson --j my_project:US.bquijob_123x456_789y123z456c
A saída contém muitos campos, incluindo searchStatistics, que é
parecido com o seguinte. Neste exemplo, indexUsageMode indica que o
índice não foi usado. Isso acontece quando a tabela não tem um índice de pesquisa. Para
resolver esse problema, crie um índice de pesquisa na
tabela. Consulte o
indexUnusedReason campo code
para ver uma lista de todos os motivos pelos quais um índice de pesquisa pode não ser usado em uma consulta.
"searchStatistics": {
"indexUnusedReasons": [
{
"baseTable": {
"datasetId": "my_dataset",
"projectId": "my_project",
"tableId": "my_table"
},
"code": "INDEX_CONFIG_NOT_AVAILABLE",
"message": "There is no search index configuration for the base table `my_project:my_dataset.my_table`."
}
],
"indexUsageMode": "UNUSED"
},
Consultar visualizações INFORMATION_SCHEMA
Também é possível ver o uso do índice da Pesquisa para vários jobs em uma região nas seguintes visualizações:
INFORMATION_SCHEMA.JOBSINFORMATION_SCHEMA.JOBS_BY_USERINFORMATION_SCHEMA.JOBS_BY_FOLDERINFORMATION_SCHEMA.JOBS_BY_ORGANIZATION
A consulta a seguir mostra informações sobre o uso de índices para todas as consultas otimizáveis de índice da Pesquisa nos últimos sete dias:
SELECT job_id, search_statistics.index_usage_mode, index_unused_reason.code, index_unused_reason.base_table.table_id, index_unused_reason.index_name FROM `region-REGION_NAME`.INFORMATION_SCHEMA.JOBS, UNNEST(search_statistics.index_unused_reasons) AS index_unused_reason WHERE end_time BETWEEN TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) AND CURRENT_TIMESTAMP();
O resultado será semelhante ao seguinte:
+-----------+----------------------------------------+-----------------------+ | job_id | index_usage_mode | code | table_id | index_name | +-----------+------------------+---------------------+-----------------------+ | bquxjob_1 | UNUSED | BASE_TABLE_TOO_SMALL| my_table | my_index | | bquxjob_2 | FULLY_USED | NULL | my_table | my_index | +-----------+----------------------------------------+-----------------------+
Práticas recomendadas
As seções a seguir descrevem as práticas recomendadas para pesquisar.
Pesquisar seletivamente
A pesquisa funciona melhor quando a pesquisa tem poucos resultados. Torne as pesquisas o mais específicas possível.
Otimização da função ORDER BY LIMIT
As consultas que usam SEARCH, =, IN, LIKE ou STARTS_WITH em uma tabela
particionada muito grande podem ser otimizadas
quando você vai usar uma cláusula ORDER BY no campo particionado e uma cláusula LIMIT.
Para consultas que não contêm a função SEARCH, é possível usar os
outros operadores e funções para
aproveitar a otimização. A otimização será aplicada mesmo que a tabela base
não seja indexada. Isso funciona bem se você estiver procurando um termo comum.
Por exemplo, suponha que a tabela Logs criada anteriormente seja particionada em uma coluna com tipo DATE adicional chamada day. A consulta a seguir é otimizada:
SELECT Level, Source, Message FROM my_dataset.Logs WHERE SEARCH(Message, "foo") ORDER BY day LIMIT 10;
Defina o escopo da sua pesquisa
Quando você usar a função SEARCH, pesquise somente as colunas da tabela que devem conter os termos de pesquisa. Isso melhora o desempenho e
reduz o número de bytes que precisam ser verificados.
Use crase
Quando você usa a função SEARCH com o analisador de texto LOG_ANALYZER,
incluir a consulta de pesquisa entre crases
força uma correspondência exata. Isso é útil
quando a pesquisa diferencia maiúsculas de minúsculas ou contém caracteres que não devem ser
interpretados como delimitadores. Por exemplo, para pesquisar o endereço IP 192.0.2.1, use `192.0.2.1`. Sem as crases, a pesquisa retorna qualquer linha que
contenha os tokens individuais 192, 0, 2 e 1, em qualquer ordem.