Cercare i dati indicizzati
Questa pagina fornisce esempi di ricerca in BigQuery.
Quando indicizzi i dati, BigQuery può ottimizzare alcune query
che utilizzano la funzione SEARCH
o altri operatori e funzioni,
come =
, IN
, LIKE
e STARTS_WITH
.
Le query SQL restituiscono risultati corretti da tutti i dati importati, anche se alcuni non sono ancora indicizzati. Tuttavia, le prestazioni delle query possono essere notevolmente migliorate con un indice. I risparmi in termini di byte elaborati e millisecondi di slot vengono massimizzati quando il numero di risultati di ricerca rappresenta una frazione relativamente piccola delle righe totali della tabella perché vengono scansionati meno dati. Per determinare se un indice è stato utilizzato per una query, consulta Utilizzo dell'indice di ricerca.
Creare un indice di ricerca
La seguente tabella denominata Logs
viene utilizzata per mostrare diversi modi di utilizzare la funzione SEARCH
. Questa tabella di esempio è piuttosto piccola, ma in pratica i miglioramenti delle prestazioni ottenuti con SEARCH
aumentano con le dimensioni della tabella.
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' );
La tabella avrà il seguente aspetto:
+---------+----------------+-------------------------------------------------------+ | 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 | +---------+----------------+-------------------------------------------------------+
Crea un indice di ricerca nella tabella Logs
utilizzando l'analizzatore di testo predefinito:
CREATE SEARCH INDEX my_index ON my_dataset.Logs(ALL COLUMNS);
Per saperne di più sugli indici di ricerca, consulta Gestire gli indici di ricerca.
Utilizzare la funzione SEARCH
La funzione SEARCH
fornisce la ricerca tokenizzata nei dati.
SEARCH
è progettato per essere utilizzato con un indice per ottimizzare le ricerche.
Puoi utilizzare la funzione SEARCH
per cercare in un'intera tabella o limitare la ricerca a colonne specifiche.
Cercare in un'intera tabella
La seguente query cerca il valore bar
in tutte le colonne della tabella Logs
e restituisce le righe che contengono questo valore, indipendentemente dalle lettere maiuscole. Poiché l'indice di ricerca utilizza l'analizzatore di testo predefinito, non è necessario specificarlo nella funzione 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 | +---------+----------------+-------------------------------------------------------+
La seguente query cerca il valore `94.60.64.181`
in tutte le colonne della tabella Logs
e restituisce le righe che contengono questo valore. I segni di a capo consentono una ricerca esatta, motivo per cui l'ultima riga della tabella Logs
che contiene 181.94.60.64
viene omessa.
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 | +---------+----------------+-------------------------------------------------------+
Cercare in un sottoinsieme di colonne
SEARCH
consente di specificare facilmente un sottoinsieme di colonne in cui cercare i dati. La seguente query cerca il valore 94.60.64.181
nella colonna Message
della tabella Logs
e restituisce le righe che contengono questo valore.
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 | +---------+----------------+-------------------------------------------------------+
La seguente query cerca sia le colonne Source
che Message
della tabella
Logs
. Restituisce le righe contenenti il valore 94.60.64.181
da entrambe le colonne.
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 | +---------+----------------+-------------------------------------------------------+
Escludere colonne da una ricerca
Se una tabella ha molte colonne e vuoi cercarne la maggior parte, potrebbe essere più facile specificare solo le colonne da escludere dalla ricerca. La seguente query cerca in tutte le colonne della tabella Logs
, tranne nella colonna Message
. Restituisce le righe di qualsiasi colonna diversa da Message
che contiene il valore 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 | +---------+----------------+---------------------------------------------------+
Utilizzare un altro analizzatore di testo
Il seguente esempio crea una tabella denominata contact_info
con un indice che utilizza l'analizzatore di testo 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 | +---------+---------------------+
La seguente query cerca Kim
nella colonna name
e kim
nella colonna email
.
Poiché l'indice di ricerca non utilizza l'analizzatore di testo predefinito, devi passare il nome dell'analizzatore alla funzione 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;
NO_OP_ANALYZER
non modifica il testo, pertanto la funzione SEARCH
restituisce TRUE
solo per le corrispondenze esatte:
+---------+----------+---------------------+-----------+ | 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 | +---------+----------+---------------------+-----------+
Configurare le opzioni dell'analizzatore di testo
Gli strumenti di analisi del testo LOG_ANALYZER
e PATTERN_ANALYZER
possono essere personalizzati aggiungendo una stringa in formato JSON alle opzioni di configurazione. Puoi configurare gli analizzatori di testo nella funzione SEARCH
, nell'istruzione DDL CREATE
SEARCH INDEX
e nella funzione TEXT_ANALYZE
.
L'esempio seguente crea una tabella denominata complex_table
con un indice che utilizza l'analizzatore di testo LOG_ANALYZER
. Utilizza una stringa in formato JSON per configurare le opzioni dell'analizzatore:
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": [ { "normalization": {"mode": "NONE"} } ] }''');
Le tabelle seguenti mostrano esempi di chiamate alla funzione SEARCH
con diversi analizzatori di testo e i relativi risultati. La prima tabella chiama la funzione SEARCH
utilizzando l'analizzatore di testo predefinito, LOG_ANALYZER
:
Chiamata di funzione | Restituisce | Motivo |
---|---|---|
RICERCA('foobarexample', NULL) | ERRORE | La colonna termini_ricerca è "NULL". |
SEARCH('foobarexample', '') | ERRORE | search_terms non contiene token. |
RICERCA('foobar-example'; 'foobar example') | VERO | "-" e " '" sono delimitatori. |
RICERCA('foobar-example'; 'foobarexample') | FALSE | La colonna search_terms non è suddivisa. |
RICERCA('foobar-example'; 'foobar\\&example') | VERO | La doppia barra rovesciata esegue l'escape dell'e commerciale, che è un delimitatore. |
RICERCA('foobar-example'; R'foobar\&example') | VERO | La barra rovesciata singola interpreta letteralmente l'e commerciale in una stringa non elaborata. |
RICERCA('foobar-example'; '`foobar&example`') | FALSE | Le barre graffe richiedono una corrispondenza esatta per foobar&example. |
RICERCA('foobar&example'; '`foobar&example`') | VERO | Viene trovata una corrispondenza esatta. |
RICERCA('foobar-example'; 'example foobar') | VERO | L'ordine dei termini non è importante. |
RICERCA('foobar-example'; 'foobar example') | VERO | I token vengono scritti in minuscolo. |
RICERCA('foobar-example'; '`foobar-example`') | VERO | Viene trovata una corrispondenza esatta. |
SEARCH('foobar-example', '`foobar`') | FALSE | Le barre graffe mantengono le lettere maiuscole. |
RICERCA('`foobar-example`'; '`foobar-example`') | FALSE | Le barre graffe non hanno un significato speciale per data_to_search e |
RICERCA('foobar@example.com'; '`example.com`') | VERO | Viene trovata una corrispondenza esatta dopo il delimitatore in dati_da_cercare. |
RICERCA('a foobar-example b'; '`foobar-example`') | VERO | Viene trovata una corrispondenza esatta tra i delimitatori di spazi. |
SEARCH(['foobar', 'example'], 'foobar example') | FALSE | Nessuna singola voce dell'array corrisponde a tutti i termini di ricerca. |
RICERCA('foobar='; '`foobar\\=`') | FALSE | search_terms è equivalente a foobar\=. |
SEARCH('foobar=', R'`foobar\=`') | FALSE | Questo è equivalente all'esempio precedente. |
RICERCA('foobar='; 'foobar\\=') | VERO | Il segno di uguale è un delimitatore nei dati e nella query. |
SEARCH('foobar=', R'foobar\=') | VERO | Questo è equivalente all'esempio precedente. |
RICERCA('foobar.example'; '`foobar`') | VERO | Viene trovata una corrispondenza esatta. |
RICERCA('foobar.example'; '`foobar.`') | FALSE | "foobar." non viene analizzato a causa delle barre graffe. |
CERCA('foobar..example'; '`foobar.`') | VERO | "foobar." non viene analizzato a causa dei backtick; è seguito |
La tabella seguente mostra esempi di chiamate alla funzione SEARCH
che utilizzano l'analizza testi NO_OP_ANALYZER
e i motivi dei vari valori restituiti:
Chiamata di funzione | Restituisce | Motivo |
---|---|---|
SEARCH('foobar', 'foobar', analyzer=>'NO_OP_ANALYZER') | VERO | Viene trovata una corrispondenza esatta. |
SEARCH('foobar', '`foobar`', analyzer=>'NO_OP_ANALYZER') | FALSE | Le barre graffe non sono caratteri speciali per NO_OP_ANALYZER. |
SEARCH('foobar', 'foobar', analyzer=>'NO_OP_ANALYZER') | FALSE | Le lettere maiuscole non corrispondono. |
SEARCH('foobar example', 'foobar', analyzer=>'NO_OP_ANALYZER') | FALSE | Non sono presenti delimitatori per NO_OP_ANALYZER. |
SEARCH('', '', analyzer=>'NO_OP_ANALYZER') | VERO | Non sono presenti delimitatori per NO_OP_ANALYZER. |
Altri operatori e funzioni
Puoi eseguire ottimizzazioni dell'indice di ricerca con diversi operatori, funzioni e predicati.
Ottimizzare con operatori e funzioni di confronto
BigQuery può ottimizzare alcune query che utilizzano l'operatore di uguaglianza (=
), l'operatore IN
, l'operatore LIKE
o la funzione STARTS_WITH
per confrontare i valori letterali di stringa con i dati indicizzati.
Ottimizzazione con i predicati di stringa
I seguenti predicati sono idonei per l'ottimizzazione dell'indice di ricerca:
column_name = 'string_literal'
'string_literal' = column_name
struct_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', ...)
STARTS_WITH(column_name, 'prefix')
column_name LIKE 'prefix%'
Ottimizzazione con i predicati numerici
Se l'indice di ricerca è stato creato con tipi di dati numerici, BigQuery può ottimizzare alcune query che utilizzano l'operatore di uguaglianza (=
) o l'operatore IN
con dati indicizzati. I seguenti predicati sono idonei per l'ottimizzazione dell'indice di ricerca:
INT64(json_column.int64_field) = 1
int64_column = 1
int64_array_column[OFFSET(0)] = 1
int64_column IN (1, 2)
struct_column.nested_int64_field = 1
struct_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")
Ottimizza le funzioni che producono dati indicizzati
BigQuery supporta l'ottimizzazione dell'indice di ricerca quando determinate funzioni vengono applicate ai dati indicizzati.
Se l'indice di ricerca utilizza l'analizzatore di testo LOG_ANALYZER
predefinito, puoi applicare le funzioni UPPER
o LOWER
alla colonna, ad esempio UPPER(column_name) = 'STRING_LITERAL'
.
Per i dati di stringa scalari JSON
estratti da una colonna JSON
indicizzata, puoi applicare la funzione STRING
o la relativa versione sicura SAFE.STRING
.
Se il valore JSON
estratto non è una stringa, la funzione STRING
genera un errore e la funzione SAFE.STRING
restituisce NULL
.
Per i dati STRING
(non JSON
) in formato JSON indicizzati, puoi applicare le seguenti funzioni:
Ad esempio, supponiamo di avere la seguente tabella indicizzata denominata
dataset.person_data
con una colonna JSON
e una STRING
:
+----------------------------------------------------------------+-----------------------------------------+ | json_column | string_column | +----------------------------------------------------------------+-----------------------------------------+ | { "name" : "Ariel", "email" : "cloudysanfrancisco@gmail.com" } | { "name" : "Ariel", "job" : "doctor" } | +----------------------------------------------------------------+-----------------------------------------+
Le seguenti query sono idonee per l'ottimizzazione:
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');
Sono ottimizzate anche le combinazioni di queste funzioni, come
UPPER(JSON_VALUE(json_string_expression)) = 'FOO'
.
Utilizzo dell'indice della Ricerca
Per determinare se è stato utilizzato un indice di ricerca per una query, esamina le Informazioni job della query in Risultati delle query. I campi Modalità di utilizzo dell'indice e Motivi del mancato utilizzo dell'indice forniscono informazioni dettagliate sull'utilizzo dell'indice di ricerca.
Le informazioni sull'utilizzo dell'indice di ricerca sono disponibili anche tramite il
campo searchStatistics
nel metodo dell'API Jobs.Get. Il campo indexUsageMode
in searchStatistics
indica se è stato utilizzato un indice di ricerca con i seguenti valori:
UNUSED
: non è stato utilizzato alcun indice di ricerca.PARTIALLY_USED
: parte della query ha utilizzato gli indici di ricerca e parte no.FULLY_USED
: ogni funzioneSEARCH
nella query utilizzava un indice di ricerca.
Quando indexUsageMode
è UNUSED
o PARTIALLY_USED
, il campo indexUnusuedReasons
contiene informazioni sul motivo per cui gli indici di ricerca non sono stati utilizzati nella query.
Per visualizzare searchStatistics
per una query, esegui il comando bq show
.
bq show --format=prettyjson -j JOB_ID
Esempio
Supponiamo che tu esegua una query che chiama la funzione SEARCH
sui dati di una tabella. Puoi visualizzare i dettagli del job della query per trovare l'ID job, quindi eseguire il comando bq show
per visualizzare ulteriori informazioni:
bq show --format=prettyjson --j my_project:US.bquijob_123x456_789y123z456c
L'output contiene molti campi, tra cui searchStatistics
, che è simile al seguente. In questo esempio, indexUsageMode
indica che l'indice non è stato utilizzato. Il motivo è che la tabella non ha un indice di ricerca. Per risolvere questo problema, crea un indice di ricerca nella tabella. Consulta il
indexUnusedReason
code
campo
per un elenco di tutti i motivi per cui un indice di ricerca potrebbe non essere utilizzato in una query.
"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"
},
Best practice
Le seguenti sezioni descrivono le best practice per la ricerca.
Cercare in modo selettivo
La ricerca funziona meglio quando i risultati sono pochi. Fai in modo che le tue ricerche siano il più specifiche possibile.
Ottimizzazione ORDER BY LIMIT
Le query che utilizzano SEARCH
, =
, IN
, LIKE
o STARTS_WITH
in una tabella molto grande
partizionata possono essere ottimizzate
quando utilizzi una clausola ORDER BY
sul campo partizionato e una clausola LIMIT
.
Per le query che non contengono la funzione SEARCH
, puoi utilizzare gli altri operatori e funzioni per sfruttare l'ottimizzazione. L'ottimizzazione viene applicata indipendentemente dal fatto che la tabella sia indicizzata o meno. Questo approccio è ideale se stai cercando un termine comune.
Ad esempio, supponiamo che la tabella Logs
creata in precedenza sia partizionata in base a un'altra colonna di tipo DATE
chiamata day
. La seguente query è ottimizzata:
SELECT Level, Source, Message FROM my_dataset.Logs WHERE SEARCH(Message, "foo") ORDER BY day LIMIT 10;
Definire l'ambito della ricerca
Quando utilizzi la funzione SEARCH
, cerca solo nelle colonne della tabella che
prevedi contengano i termini di ricerca. In questo modo, le prestazioni vengono migliorate e si riduce il numero di byte da analizzare.
Utilizzare i backtick
Quando utilizzi la funzione SEARCH
con l'analizzatore di testo LOG_ANALYZER
, inserire la query di ricerca tra barre verticali forza una corrispondenza esatta. Questa opzione è utile se la ricerca è sensibile alle maiuscole o contiene caratteri che non devono essere interpretati come delimitatori. Ad esempio, per cercare l'indirizzo IP
192.0.2.1
, utilizza `192.0.2.1`
. Senza le barre graffe, la ricerca restituisce qualsiasi riga contenente i singoli token 192
, 0
, 2
e 1
, in qualsiasi ordine.