5

Şu anda, yanıtlanmış soruya göre kullanıcılarla eşleşen bir webapp yazıyorum. Eşleme algoritmamı sadece bir sorguda gerçekleştirdim ve şu ana kadar 2 kullanıcı arasındaki eşleşme yüzdesini hesaplamak için 8.2ms almasını sağladım. Ancak web sunucumun bir kullanıcı listesini alması ve bu sorguyu gerçekleştiren listede yinelemesi gerekiyor. 5000 kullanıcı için yerel makinemde 50 sn sürdü. Kullanıcı_kimliği ve bir sütun hesaplanan eşleme ile bir sütun döndüren bir sorguya her şeyi koymak mümkün mü? Veya saklı bir prosedür bir seçenek midir?SQL: kullanıcı yüzdesini, eşleşme yüzdesi için hesaplanan sütunla döndürün?

Şu anda MySQL ile çalışıyorum ancak gerekirse veritabanlarını değiştirmeye istekliyim. http://sqlfiddle.com/#!2/84233/1

ve benim eşleme sorgusu:

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

Sorgunun iki "ayağının" "ortak soruları" alt ifadesini birleştirebilirsiniz. Ayrıca, kullanıcı = 1 ve kullanıcı = 101 alt sorgularını bir genelleştirilmiş CTE sorgusuna genellestirebilirsiniz (DBMS'niz onları destekliyorsa. Ama önce: Lütfen tablo tanımlarını ve belki de bazı verileri gösterin. – wildplasser

+0

Evet, istenen çıktıya sahip veriler –

+1

Benimle oynamak için bir SQLFiddle oluşturdum :) Kullanıcı 1 ve 5'i eşleştirdiğimde sonuç '43 .678 'olmalıdır. Http://sqlfiddle.com/#!2/84233/1 – Mexxer

cevap

1
Canım sıkıldı

, böylece: İşte sorgunun yeniden yazılmış versiyonu

şema ve veri ilgilenen herkes için, bir SQLFiddle oluşturduk - bir kerede tüm kullanıcı eşlendirmede eşleşmeleri hesaplar - senin şemanın bir PostgreSQL liman göre:

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

Kontrol ettim ve kullanıcı çifti için aynı sonuçları üretiyor (1,5). CTE okunabilmesi için sadece orada olduğunu ve FROM (SELECT ...) ile yapamaz şey yapmaz olarak

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; 

Eğer liman MySQL için bu geri yapamadı hiçbir neden yoktur. WITH RECURSIVE yan tümcesi yok ve birden fazla CTE'den CTE başvurulmuyor. Biraz korkutucu iç içe geçmiş bir sorgunuz olurdu, ama bu sadece bir biçimlendirme zorluğu.

değişiklikler:

  • kullanıcı çiftlerinde bir dizi oluşturmak için farklı kullanıcıların bu seti Kendinden katılmak ayrı bir kullanıcı grubu oluşturun ve ardından skoru eşleştirmeler o listede katılmak sorgu bir tablo üretmek için sorgu
  • possiblescore1 ve possiblescore2, actualscore1 ve actualscore2 için büyük ölçüde yinelenen sorguları birleştirerek skor tablosunu oluşturun.
  • sonra sorgu optimize değil

nihai dış sorguda bunu özetlemek; Yazıldığı gibi sistemimde 5ms çalışır. Daha büyük veride, bir kısmını yeniden yapılandırmanız veya bazı CTE cümlelerini, sorgulamadan önce dizine eklediğiniz SELECT ... INTO TEMPORARY TABLE temp tablo oluşturma ifadelerine dönüştürmek gibi hileler kullanmanız gerekebilir.

Ayrıca, users satır grubunun üretimini CTE'den dışarı çıkarmak ve alt FROM alt sorgusuna taşımak isteyebilirsiniz. Çünkü, WITH'un, maddeler arasında bir optimizasyon çiti olarak davranması gerekir, bu nedenle veritabanı satırları somutlaştırmalı ve maddeleri yukarı veya aşağı itmek gibi hileleri kullanamaz.