5

Attualmente sto scrivendo una webapp che corrisponde agli utenti in base alla domanda con risposta. Ho realizzato il mio algoritmo di corrispondenza in una sola query e l'ho ottimizzato fino ad ora che sono necessari 8,2 ms per calcolare la percentuale di corrispondenza tra 2 utenti. Ma la mia webapp deve prendere un elenco di utenti e scorrere l'elenco che esegue questa query. Per 5000 utenti ci sono voluti 50 secondi sul mio computer locale. È possibile inserire tutto in una query che restituisce una colonna con user_id e una colonna con la corrispondenza calcolata? O una stored procedure è un'opzione?SQL: tabella utente di ritorno con colonna calcolata per percentuale di corrispondenza?

Attualmente sto lavorando con MySQL ma sono disposto a cambiare database se necessario.

Per chiunque sia interessato nello schema e dati, ho creato uno SQLFiddle: http://sqlfiddle.com/#!2/84233/1

e la mia query corrispondente:

SELECT COALESCE(SQRT((100.0*as1.actual_score/ps1.possible_score) * (100.0*as2.actual_score/ps2.possible_score)) - (100/ps1.commonquestions), 0) AS perc 
    FROM (SELECT SUM(imp.value) AS actual_score 
     FROM user_questions AS uq1 
     INNER JOIN importances imp ON imp.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq2.question_id = uq1.question_id AND uq2.user_id = 101 
     AND (uq1.accans1 = uq2.answer_id 
      OR uq1.accans2 = uq2.answer_id 
      OR uq1.accans3 = uq2.answer_id 
      OR uq1.accans4 = uq2.answer_id) 
     WHERE uq1.user_id = 1) AS as1, 
    (SELECT SUM(value) AS possible_score, COUNT(*) AS commonquestions 
     FROM user_questions AS uq1 
     INNER JOIN importances ON importances.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq1.question_id = uq2.question_id AND uq2.user_id = 101 
     WHERE uq1.user_id = 1) AS ps1, 
    (SELECT SUM(imp.value) AS actual_score 
     FROM user_questions AS uq1 
     INNER JOIN importances imp ON imp.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq2.question_id = uq1.question_id AND uq2.user_id = 1 
     AND (uq1.accans1 = uq2.answer_id 
      OR uq1.accans2 = uq2.answer_id 
      OR uq1.accans3 = uq2.answer_id 
      OR uq1.accans4 = uq2.answer_id) 
     WHERE uq1.user_id = 101) AS as2, 
    (SELECT SUM(value) AS possible_score 
     FROM user_questions AS uq1 
     INNER JOIN importances ON importances.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq1.question_id = uq2.question_id AND uq2.user_id = 1 
     WHERE uq1.user_id = 101) AS ps2 
+1

È possibile combinare la sottoespressione "domande comuni" delle due "gambe" della query. È inoltre possibile generalizzare le sottoquery per utente = 1 e utente = 101 in una query CTE generalizzata (se i DBMS li suppongono, ma prima: per favore mostraci le definizioni di tabella e forse alcuni dati. – wildplasser

+0

Sì, dati con la rispettiva uscita desiderata –

+1

Ho creato un SQLFiddle con cui giocare :) Quando abbino gli utenti 1 e 5 il risultato dovrebbe essere '43 .678 'http://sqlfiddle.com/#!2/84233/1 – Mexxer

risposta

1

mi annoiavo, così: Ecco una versione riscritta di query - sulla base di un porto PostgreSQL dello schema - che calcola le partite per tutte le associazioni di utenti in una volta:

http://sqlfiddle.com/#!12/30524/6

Ho controllato e produce gli stessi risultati per la coppia utente (1,5).

WITH 
userids(uid) AS (
    select distinct user_id from user_questions 
), 
users(u1,u2) AS (
    SELECT u1.uid, u2.uid FROM userids u1 CROSS JOIN userids u2 WHERE u1 <> u2 
), 
scores AS (
     SELECT 
      sum(CASE WHEN uq2.answer_id IN (uq1.accans1, uq1.accans2, uq1.accans3, uq1.accans4) THEN imp.value ELSE 0 END) AS actual_score, 
      sum(imp.value) AS potential_score, 
      count(1) AS common_questions, 
      users.u1, 
      users.u2 
     FROM user_questions AS uq1 
     INNER JOIN importances imp ON imp.id = uq1.importance 
     INNER JOIN user_questions uq2 ON uq2.question_id = uq1.question_id 
     INNER JOIN users ON (uq1.user_id=users.u1 AND uq2.user_id=users.u2) 
     GROUP BY u1, u2 
), 
score_pairs(u1,u2,u1_actual,u2_actual,u1_potential,u2_potential,common_questions) AS (
    SELECT s1.u1, s1.u2, s1.actual_score, s2.actual_score, s1.potential_score, s2.potential_score, s1.common_questions 
    FROM scores s1 INNER JOIN scores s2 ON (s1.u1 = s2.u2 AND s1.u2 = s2.u1) 
    WHERE s1.u1 < s1.u2 
) 
SELECT 
    u1, u2, 
    COALESCE(SQRT((100.0*u1_actual/u1_potential) * (100.0*u2_actual/u2_potential)) - (100/common_questions), 0) AS "match" 
FROM score_pairs; 

Non c'è alcun motivo per cui non poteva porta questo ritorno a MySQL, come il CTE è lì solo per leggibilità e non fa nulla non si può fare con FROM (SELECT ...). Non c'è la clausola WITH RECURSIVE e nessun CTE fa riferimento a più di un altro CTE. Avresti una query nidificata spaventosa, ma questa è solo una sfida di formattazione.

Modifiche:

  • generare un insieme di utenti distinti
  • Self-join che gruppo di utenti distinti per creare una serie di abbinamenti utente
  • e poi unirsi in tale elenco degli abbinamenti nel punteggio eseguire una query per produrre una tabella di punteggi
  • Produrre la tabella dei punteggi combinando le query in gran parte duplicate per possiblescore1 e possiblescore2, actualscore1 e actualscore2.
  • poi riassumere nella query esterna finale

non ho ottimizzato la query; come scritto funziona in 5ms sul mio sistema. Su dati più grandi è possibile che sia necessario ristrutturarne alcuni o utilizzare trucchi come convertire alcune clausole CTE in istruzioni di creazione tabella temporanea SELECT ... INTO TEMPORARY TABLE che si indicizzano prima di eseguire una query.

È anche possibile spostare la generazione del set di righe users dal CTE e in una clausola di subquery FROM. Questo perché WITH è necessario comportarsi come una fence di ottimizzazione tra clausole, quindi il database deve materializzare le righe e non può utilizzare trucchi come le clausole di push su o giù.