5

Obecnie piszę aplikację internetową, która dopasowuje użytkowników do odpowiedzi na pytanie. Zrobiłem mój algorytm dopasowania w jednym zapytaniu i dostroiłem go do tego stopnia, że ​​zajmuje 8.2ms, aby obliczyć procent dopasowania między 2 użytkownikami. Ale moja aplikacja musi pobrać listę użytkowników i przejrzeć listę wykonującą to zapytanie. Dla 5000 użytkowników zajęło 50 sekund na mojej lokalnej maszynie. Czy jest możliwe umieszczenie wszystkiego w jednym zapytaniu, które zwraca jedną kolumnę z identyfikatorem użytkownika i jedną kolumną z wyliczonym dopasowaniem? Czy jest procedura przechowywana opcja?SQL: zwraca tabelę użytkowników z kolumną obliczoną dla procentu dopasowania?

Obecnie pracuję z MySQL, ale w razie potrzeby chcę zmienić bazę danych.

Dla wszystkich zainteresowanych schematu i danych, jakie stworzył SQLFiddle: http://sqlfiddle.com/#!2/84233/1

i moje zapytanie Dopasowanie:

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

Można połączyć podwyrażenie "zwykłe pytania" dwóch "nóg" zapytania. Można również uogólnić podzapytania dla user = 1 i user = 101 na jedno ogólne zapytanie CTE (jeśli DBMS je obsługuje, ale najpierw: Pokaż nam definicje tabel i być może niektóre dane. – wildplasser

+0

Tak, dane z odpowiednim pożądanym wyjściem –

+1

Stworzyłem SQLFiddle do grania :) Kiedy pasuję do użytkownika 1 i 5, wynik powinien wynosić '43 .678 'http://sqlfiddle.com/#!2/84233/1 – Mexxer

Odpowiedz

1

byłem znudzony, więc: Oto przepisany wersja zapytaniu - na podstawie portu PostgreSQL swojego schematu - który oblicza wyniki dla wszystkich par użytkowników naraz:

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

Sprawdziłem i daje to samo wyniki dla pary użytkowników (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; 

Nie ma powodu, że nie mógł tego portu z powrotem do MySQL, jak CTE jest tam tylko dla czytelności i nic nie można zrobić z FROM (SELECT ...) nie. Nie ma klauzuli WITH RECURSIVE i nie ma odniesienia do CTE z więcej niż jednego innego CTE. Miałbyś trochę przerażającego zapytania zagnieżdżonego, ale to tylko wyzwanie formatowania.

Zmiany:

  • wygenerować zestaw różnych użytkowników
  • samosprzężenie to zbiór różnych użytkowników, aby utworzyć zestaw par użytkowników
  • a następnie dołączyć na tej liście par w partyturze zapytanie do utworzenia tabeli wyników
  • Stwórz tabelę wyników, łącząc w dużym stopniu duplikaty zapytań dla possiblescore1 i possiblescore2, actualscore1 i actualscore2.
  • następnie podsumować w końcowym zapytania zewnętrznej

nie zoptymalizowano zapytania; jak napisano, działa w moim systemie 5ms. W przypadku większych danych możliwe, że będziesz musiał zmienić niektóre z nich lub użyć sztuczek, takich jak przekształcanie niektórych klauzul CTE w instrukcje tworzenia tabel tymczasowych SELECT ... INTO TEMPORARY TABLE, które następnie indeksujesz przed wysłaniem zapytania.

Możliwe jest również przeniesienie generowania zestawu zestawów users z CTE i do klauzuli podkwerendy FROM z scores. Dzieje się tak, ponieważ WITH jest wymagane, aby zachowywać się jak blokada optymalizacji między klauzulami, więc baza danych musi zmaterializować wiersze i nie może wykorzystywać sztuczek takich jak zwiększanie lub zmniejszanie klauzul.

Powiązane problemy