2009-09-02 6 views
5

Non ho mai fatto ricerche da MYSQL prima, ma ho bisogno di implementare una ricerca. Ho tre tabelle, 'articoli', 'articoli_tags' e 'tag'.MySQL Ricerca full-text booleana con tag

'articoli' contiene la prima cosa che vorrei cercare, il campo 'titolo'.

'articles_tags' è una tabella pivot che collega 'articoli' e 'tag' insieme. 'articles_tags' ha due campi: 'articles_id' e 'tag_id'.

"tag" contiene la seconda cosa su cui vorrei cercare, il campo "nome".

Il mio problema è che ho bisogno di un modo per cercare il campo 'titolo' e ogni tag relativo a quell'articolo ('tags.name') e restituire una pertinenza (o ordinamento per pertinenza) per l'articolo.

Quale sarebbe un buon modo per implementarlo? Sono abbastanza sicuro che non può essere fatto da una sola query quindi due query, e quindi 'mescolando' le pertinenze insieme, sarebbe ok.

Grazie.

Modifica: Ho dimenticato di dire che se potessi dare più importanza alla corrispondenza di un tag rispetto a una parola nel titolo, sarebbe fantastico. Non sto davvero chiedendo a nessuno di scrivere la cosa, ma dammi una certa direzione. Sono un po 'newbie sia in PHP che in MySQL.

risposta

0

divertente è la terza domanda circa più o meno lo stesso problema che vedo in 2 giorni, prova anche questi due post: 1, 2

+0

Ho guardato quei due, ma non riescono a vedere come si riferiscono alla mia problema. –

+2

La cosa più divertente è che questo è davvero un commento, non una risposta. – TheCarver

0

Questa query demo rapida è tutt'altro che ottimale, ma dovrebbe essere un buon punto di partenza

SELECT * FROM 
(SELECT a.id, a.title, 
    MATCH (a.title) AGAINST ('$s_search_term') AS title_score, 
    SUM(MATCH (t.name) AGAINST ('$s_search_term') 
) AS tag_score 
FROM articles AS a 
LEFT JOIN articles_tags AS at 
    ON a.id = at.article_id 
LEFT JOIN tags AS t 
    ON t.id = at.tag_id 
WHERE MATCH (a.title) AGAINST ('$s_search_term') 
    OR MATCH (t.name) AGAINST ('$s_search_term') 
GROUP BY a.id) AS table1 
ORDER BY 2*tag_score + title_score DESC 

si consiglia di normalizzare tag_score dividendolo per COUNT (t.id). Scusate ma è più facile fornire la query che spiegare come realizzarla.

2

A partire dalla risposta fornita da @ james.c.funk ma apportando alcune modifiche.

SELECT a.id, a.title, 
    MATCH (a.title) AGAINST (?) AS relevance 
FROM articles AS a 
LEFT OUTER JOIN (articles_tags AS at 
    JOIN tags AS t ON (t.id = at.tag_id AND t.name = ?)) 
    ON (a.id = at.article_id) 
WHERE MATCH (a.title) AGAINST (? IN BOOLEAN MODE) 
ORDER BY IF(t.name IS NOT NULL, 1.0, relevance) DESC; 

Presumo che si desidera che le corrispondenze di tag corrispondano alla stringa intera, invece di utilizzare una ricerca di testo completo.

Utilizzare anche un join esterno sinistro anziché due, perché se un join su articles_tags è soddisfatto, allora sicuramente c'è un tag. Inserire il confronto del nome del tag all'interno della condizione di join anziché nella clausola WHERE.

La modalità booleana rende MATCH() restituisce 1.0 su una corrispondenza, che lo rende inutile come una misura di rilevanza. Quindi fare un confronto in più nella select-list per calcolare la pertinenza. Questo valore è compreso tra 0.0 e 1.0. Ora possiamo fare in modo che la corrispondenza di un tag sia superiore considerando che ha una rilevanza di 1.0.

+0

Ciao Bill. Ho letto in alcuni punti che usare JOIN con FULLTEXT è sbagliato, poiché obbliga MySQL a eseguire una scansione completa della tabella e a perdere prestazioni importanti. Ora eseguirò un test su questo per vedere se quello che ho letto fosse vero. – TheCarver

+0

@PaparazzoKid, dipende da quale tabella si accede prima. MySQL si integra utilizzando un algoritmo del ciclo nidificato, quindi se si utilizza FULLTEXT per limitare il numero di righe corrispondenti nella prima tabella, quindi utilizzarlo per cercare le righe in una tabella unita, non dovrebbe esserci alcun problema. Ma se si esegue prima la scansione di un'altra tabella di tabella, quindi utilizzare FULLTEXT nella condizione di join o, peggio ancora, utilizzare una * colonna * della prima tabella come pattern da cercare nella ricerca FULLTEXT (non so se questa è off-line anche possibile), quindi sarebbe costoso. Potrebbe essere necessario utilizzare STRAIGHT_JOIN. –

1

Ecco come ho fatto in passato. Sembra lento, ma penso che troverai che non lo è.

Ho aggiunto un po 'di complessità per mostrare cos'altro può essere fatto facilmente.In questo esempio, un articolo otterrà 1 punto per una partita parziale del titolo, 2 punti per una partita con tag parziale, 3 punti per una corrispondenza esatta del tag e 4 punti per una corrispondenza del titolo esatta. Quindi aggiunge quelli in base al punteggio.

SELECT 
    a.*, 
    SUM(
    CASE WHEN a.title LIKE '%keyword%' THEN 1 ELSE 0 END 
    + 
    CASE WHEN t.name LIKE '%keyword%' THEN 2 ELSE 0 END 
    + 
    CASE WHEN t.name = 'keyword' THEN 3 ELSE 0 END 
    + 
    CASE WHEN a.title = 'keyword' THEN 4 ELSE END 
) AS score 
FROM article a, articles_tags at, tags t 
WHERE a.id = at.article_id 
AND at.tag_id=t.id 
AND (a.title LIKE '%keyword%' OR t.name LIKE '%keyword%') 
GROUP BY a.id 
ORDER BY score; 

NOTE: Questo non restituirà gli articoli senza etichette. Ho usato join semplici per ridurre il rumore nella query ed evidenziare solo ciò che sta facendo il punteggio. Per includere articoli senza tag, basta unire i join a sinistra.

2

Vale la pena in questo momento, raccomandando che si cerchi di scaricare il lavoro di ricerca in qualcosa che è effettivamente scritto solo per quello scopo?

Nei nostri prodotti, utilizziamo MySQL per archiviare dati, ma indicizziamo tutti i nostri dati con Lucene (tramite Solr - ma ciò è irrilevante).

Vale la pena dare un'occhiata, perché è relativamente semplice da configurare, è molto potente ed è molto più semplice che cercare di manipolare il database nel fare ciò che si desidera.

Spiacente, questo non è una risposta diretta alla domanda, mi sento che questo genere di cose è sempre degno di nota in questo scenario :)

+1

come fai a mantenere mysql e lucene in sincrono? grazie –