如何比较行之间的值并找到响应的平均值?

时间:2012-04-29 17:19:12

标签: mysql sql database join subquery

我有一个MySQL表,用户对是/否投票问题的回答。看起来有点像这样:

| user_id    | poll_id  | response  |
|------------|----------|-----------|
|    111     |    1     |   'yes'   |
|    111     |    2     |   'no'    |
|    111     |    3     |   'no'    |
|    222     |    1     |   'yes'   |
|    222     |    2     |   'yes'   |
|    222     |    3     |   'yes'   |
|    333     |    1     |   'no'    |
|    333     |    2     |   'no'    |
|    333     |    3     |   'no'    |

我想计算每个用户的回复与每个其他用户的回复之间的相似性。因此,用户111和用户222是相似的0.333(因为它们具有3个相同的响应中的1个),并且用户111和用户333是0.666相似的(因为它们具有3个相同响应中的2个)。

我已经编写了一个查询,它会为我提供两个指定用户的相同响应数:

SELECT  COUNT(*) AS same_count 
FROM    (
            SELECT  response 
            FROM    results 
            WHERE   user_id = 111
        ) AS t1
    ,   (
            SELECT  response 
            FROM    results 
            WHERE   user_id = 222
        ) AS t2 
WHERE   t1.response = t2.response

现在我试图想办法让所有用户获得该信息,以产生如下结果:

| user_1  |  user_2  |  same_count  |
|---------|----------|--------------|
|  111    |   222    |    0.333     |
|  111    |   333    |    0.666     |
|  222    |   111    |    0.333     |
|  222    |   333    |    0         |
|  333    |   111    |    0.666     |
|  333    |   222    |    0         |

或者,如果可能,没有冗余信息:

| user_1  |  user_2  |  same_count  |
|---------|----------|--------------|
|  111    |   222    |    0.333     |
|  111    |   333    |    0.666     |
|  222    |   333    |    0         |

我的直觉告诉我,有一种方法可以将其作为一个可怕的MySQL查询,而不必通过PHP中的循环执行一堆查询。有人能指出我正确的方向吗?

2 个答案:

答案 0 :(得分:3)

您必须使用* poll_id *和* user_id *列对同一个表执行完全外连接。结果将显示两次,以避免我们需要以这样的方式指定条件:只有 alias1 表的user_id值小于 alias2 table的user_id值仅包含在结果集中。

Click here to view the demo in SQL Fiddle.

脚本

CREATE TABLE poll
(
    user_id     INT         NOT NULL
  , poll_id     INT         NOT NULL
  , response    VARCHAR(10) NOT NULL  
);

INSERT INTO poll (user_id, poll_id, response) VALUES
   (111, 1, 'yes'),
   (111, 2, 'no'),
   (111, 3, 'no'),
   (222, 1, 'yes'),
   (222, 2, 'yes'),
   (222, 3, 'yes'),
   (333, 1, 'no'),
   (333, 2, 'no'),
   (333, 3, 'no');

SELECT      p1.user_id AS user_1
        ,   p2.user_id AS user_2, 
            AVG(CASE 
                    WHEN p1.response = p2.response THEN 1 
                    ELSE 0 
                END) Average_Response
FROM        poll p1
,           poll p2 
WHERE       p1.poll_id = p2.poll_id 
AND         p1.user_id < p2.user_id
GROUP BY    p1.user_id
        ,   p2.user_id;

输出

USER_1 USER_2 AVERAGE_RESPONSE
------ ------ ----------------
111     222      0.3333
111     333      0.6667
222     333      0

答案 1 :(得分:1)

这可以得到你想要的结果:

SELECT
  t1.user_id AS user_1,
  t2.user_id AS user_2,
  SUM(CASE WHEN t1.response = t2.response THEN 1 ELSE 0 END) / COUNT(1)
    AS same_count
FROM t t1
JOIN t t2 ON ( t2.user_id > t1.user_id AND t2.poll_id = t1.poll_id )
GROUP BY t1.user_id, t2.user_id
ORDER BY user_1, user_2

我的测试结果:

111 222 0.333333333333333
111 333 0.666666666666667
222 333 0

CASE部分可以在MySQL中更容易编写为(t1.response = t2.response),我的版本也适用于其他类型的数据库。
这一部分通过计算所有匹配的条目,并将计数除以条目数来完成主要技巧。

t2.user_id > t1.user_id将删除重复项(111 - 222,222 - 111)。