SQL:返回带有计算列匹配百分比的用户表?

时间:2012-10-20 10:16:01

标签: mysql sql postgresql stored-procedures common-table-expression

我目前正在撰写一个基于已回答问题与用户匹配的网络应用程序。我只在一个查询中实现了我的匹配算法,并将其调整到目前为止需要8.2ms来计算2个用户之间的匹配百分比。但我的webapp必须获取用户列表并遍历执行此查询的列表。对于5000个用户,我的本地计算机需要50秒。是否可以将所有内容放在一个查询中,该查询返回一个包含user_id的列和一个包含计算匹配的列?或者存储过程是一个选项?

我目前正在使用MySQL,但愿意在需要时切换数据库。

对于对架构和数据感兴趣的任何人,我创建了一个SQLFiddle:http://sqlfiddle.com/#!2/84233/1

和我的匹配查询:

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 个答案:

答案 0 :(得分:1)

我很无聊,所以:这是您的查询的重写版本 - 基于您架构的PostgreSQL端口 - 一次计算所有用户配对的匹配项:

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

我已经检查过,它会为用户对产生相同的结果(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;

没有理由你不能将它移植回MySQL,因为CTE只是为了提高可读性而且没有做任何你能做{{1}的事情。 }。没有FROM (SELECT ...)子句,并且没有CTE从多个其他CTE引用。您有一些可怕的嵌套查询,但这只是一个格式化挑战。

的变化:

  • 生成一组不同的用户
  • 自我加入一组不同的用户以创建一组用户配对
  • 然后在分数查询中加入配对列表以生成分数表
  • 通过组合可能的score1和possiblescore2,actualscore1和actualscore2的大量重复查询来生成得分表。
  • 然后在最终的外部查询中总结它

我还没有优化查询;如我所写,它在我的系统上以5ms运行。对于更大的数据,您可能需要对其中的一些进行重组或使用一些技巧,例如将一些CTE子句转换为WITH RECURSIVE临时表创建语句,然后在查询之前将其编入索引。

您也可能希望将SELECT ... INTO TEMPORARY TABLE行集的生成移出CTE并转移到users FROM子查询子句中。这是因为scores需要在子句之间充当优化范围,因此数据库必须实现行并且不能使用诸如向上或向下推动子句等技巧。