2015-03-17 10 views
6

Ho una tabella SQLite che è stata generata utilizzando il modulo FTS4. Ogni voce è elencata almeno due volte con lingue diverse, ma continua a condividere un ID univoco (colonna int, non indicizzata). Ecco cosa voglio fare: Voglio cercare un termine in una lingua preferita. Voglio unire il risultato con una ricerca per lo stesso termine usando un'altra lingua. Per la seconda ricerca, però, voglio ignorare tutte le voci (identificate dal loro ID) che ho già trovato durante la prima ricerca. Quindi in pratica voglio fare questo:SQLite FTS4 con lingua preferita

WITH term_search1 AS (
    SELECT * 
    FROM myFts 
    WHERE myFts MATCH 'term' 
    AND languageId = 1) 
SELECT * 
FROM term_search1 
UNION 
SELECT * 
FROM myFts 
WHERE myFts MATCH 'term' 
AND languageId = 2 
AND id NOT IN (SELECT id FROM term_search1) 

Il problema qui è che la query term_seach1 verrebbe eseguita due volte. C'è un modo per materializzare i miei risultati, forse? Qualsiasi soluzione per limitarla a 2 query (anziché 3) sarebbe ottima.

Ho anche provato ad utilizzare query ricorsive, qualcosa di simile:

WITH RECURSIVE term_search1 AS (
    SELECT * 
    FROM myFts 
    WHERE myFts MATCH 'term' 
    AND languageId = 1 
UNION ALL 
    SELECT m.* 
    FROM myFts m LEFT OUTER JOIN term_search1 t ON (m.id = t.id) 
    WHERE myFts MATCH 'term' 
    AND m.languageId = 2 
    AND t.id IS NULL 
) 
SELECT * FROM term_search1 

Ciò non ha funzionato né. Apparentemente ha appena eseguito due ricerche per languageId = 2 (è forse un errore?).

Grazie in anticipo :)

risposta

4

È possibile utilizzare le tabelle temporanee per ridurre il numero di query al myFts a 2:

CREATE TEMP TABLE results (id INTEGER PRIMARY KEY); 

INSERT INTO results 
    SELECT id FROM myFts 
    WHERE myFts MATCH 'term' AND languageId = 1; 

INSERT INTO results 
    SELECT id FROM myFts 
    WHERE myFts MATCH 'term' AND languageId = 2 
    AND id NOT IN (SELECT id FROM results); 

SELECT * FROM myFts 
    WHERE id IN (SELECT id FROM results); 

DROP TABLE results; 

Se è possibile cambiare lo schema, è solo dovrebbe mantenere i dati di testo nella tabella FTS. In questo modo si eviteranno risultati errati quando si cercano numeri e file corrispondenti a languageId non desiderati. Crea un'altra meta tabella con dati non testuali (come id e languageId) e filtra le righe unendole allo rowid dello myFts. In questo modo è necessario interrogare la tabella FTS solo una volta - utilizzare la tabella temporanea per memorizzare i risultati della tabella FTS, quindi utilizzare la tabella dei meta per ordinarli.

+0

Ciao, bene fino ad oggi la creazione di una tabella temporanea è apparentemente l'unica soluzione che in qualche modo affronta il problema (sembra ancora un killer delle prestazioni quando viene eseguito spesso). Informazioni sulle cose relative allo schema .. languageId non è in realtà la mia colonna "propria". Fa parte della funzionalità FTS (una colonna nascosta). Secondo la documentazione FTS "Non è possibile per una singola query FTS restituire righe con valori languageid diversi":/Ecco perché ho sempre bisogno di almeno 2 query per questo. – Peach

+0

La creazione di una tabella temporanea è sicuramente un sovraccarico, ma potrebbe comunque essere performante. Sfortunatamente, il mio SQLite non è in grado di creare un FTS4 con estensione languageid, quindi non posso profilare le query.Qualche ragione particolare per cui hai bisogno di diversi tokenizer e non usi l'unicode61 predefinito? – Paras

2

Questo è il meglio che posso pensare:

SELECT * 
FROM myFts t1 
JOIN (SELECT COUNT(*) AS cnt, id 
     FROM myFts t2 
     WHERE t2.languageId in (1, 2) 
     AND t2.myFts MATCH 'term' 
     GROUP BY t2.id) t3 
ON t1.id = t3.id 
WHERE t1.myFts MATCH 'term' 
    AND t1.languageId in (1, 2) 
    AND (t1.languageId = 1 or t3.cnt = 1) 

Non sono sicuro se il secondo MATCH clausola è necessaria. L'idea è di contare prima le righe accettabili, quindi scegliere quella migliore.

Modifica: Non ho idea del motivo per cui non funziona con il tuo tavolo. Questo è quello che ho fatto di provarlo (SQLite versione 3.8.10.2):

CREATE VIRTUAL TABLE myFts USING fts4(
    id integer, 
    languageId integer, 
    content TEXT 
); 

insert into myFts(id, languageId, content) values (10, 1, 'term 10 lang 1'); 
insert into myFts(id, languageId, content) values (10, 2, 'term 10 lang 2'); 
insert into myFts(id, languageId, content) values (11, 1, 'term 11 lang 1'); 
insert into myFts(id, languageId, content) values (12, 2, 'term 12 lang 2'); 
insert into myFts(id, languageId, content) values (13, 1, 'not_erm 13 lang 1'); 
insert into myFts(id, languageId, content) values (13, 2, 'term 13 lang 2'); 

l'esecuzione della query dà:

sqlite> SELECT * 
    ...> FROM myFts t1 
    ...> JOIN (SELECT COUNT(*) AS cnt, id 
    ...>  FROM myFts t2 
    ...>  WHERE t2.languageId in (1, 2) 
    ...>  AND t2.myFts MATCH 'term' 
    ...>  GROUP BY t2.id) t3 
    ...> ON t1.id = t3.id 
    ...> WHERE t1.myFts MATCH 'term' 
    ...>  AND t1.languageId in (1, 2) 
    ...>  AND (t1.languageId = 1 or t3.cnt = 1); 
10|1|term 10 lang 1|2|10 
11|1|term 11 lang 1|1|11 
12|2|term 12 lang 2|1|12 
13|2|term 13 lang 2|1|13 
sqlite> 
+0

Ciao, la tua richiesta purtroppo non funzionerà. Secondo la documentazione di SQLite "Non è possibile per una singola query FTS restituire righe con valori languageid diversi":/Ecco perché devo dividerlo in 2 query in primo luogo. – Peach

+0

Strano, l'ho provato e sembrava funzionare. Come viene definita la tua tabella? – bwt

+0

Non si utilizza la funzione languageId. Prova a creare la tabella con 'CREATE VIRTUAL TABLE myFTS UTILIZZANDO fts4 (id, content, languageid =" languageId ");' Quindi anche una semplice query come 'select * from myFts dove il contenuto corrisponde a" term * ";' non restituisce risultati, perché assume 'languageId = 0'. –