基于百分比计算(*)百分比 - 使用百分比计算的复杂查询

时间:2013-11-15 19:10:37

标签: mysql sql

此查询根据用户共有多少字来建议友谊。 in_common设置此阈值。

我想知道是否有可能使此查询完全基于%。

我想要做的是让用户向当前用户推荐,如果 30%的话匹配。

curent_user总字数100

in_common threshold 30

some_other_user总字数10

其中3个匹配current_users列表。

由于3是10的30%,这是当前用户的匹配。

可能的吗

SELECT users.name_surname, users.avatar, t1.qty, GROUP_CONCAT(words_en.word) AS in_common, (users.id) AS friend_request_id
    FROM (
      SELECT c2.user_id, COUNT(*) AS qty
      FROM `connections` c1
      JOIN `connections` c2
        ON c1.user_id <> c2.user_id 
          AND c1.word_id = c2.word_id
      WHERE c1.user_id = :user_id
      GROUP BY c2.user_id
      HAVING count(*) >= :in_common) as t1
     JOIN users
       ON t1.user_id = users.id
     JOIN connections
       ON connections.user_id = t1.user_id
     JOIN words_en
       ON words_en.id = connections.word_id
     WHERE EXISTS(SELECT * 
                  FROM connections 
                  WHERE connections.user_id = :user_id
                    AND connections.word_id = words_en.id)
     GROUP BY users.id, users.name_surname, users.avatar, t1.qty
     ORDER BY t1.qty DESC, users.name_surname ASC

SQL小提琴:http://www.sqlfiddle.com/#!2/c79a6/9

3 个答案:

答案 0 :(得分:3)

好的,所以问题是“共同的用户”被定义为非对称关系。为了解决这个问题,我们假设针对用最少单词的用户检查in_common百分比阈值。

尝试此查询(fiddle),它会为您提供至少包含1个单词的完整用户列表,标记友情提示:

SELECT user1_id, user2_id, user1_wc, user2_wc,
       count(*) AS common_wc, count(*) / least(user1_wc, user2_wc) AS common_wc_pct,
       CASE WHEN count(*) / least(user1_wc, user2_wc) > 0.7 THEN 1 ELSE 0 END AS frienship_suggestion
FROM (
    SELECT u1.user_id AS user1_id, u2.user_id AS user2_id,
           u1.word_count AS user1_wc, u2.word_count AS user2_wc,
           c1.word_id AS word1_id, c2.word_id AS word2_id
      FROM connections c1
      JOIN connections c2 ON (c1.user_id < c2.user_id AND c1.word_id = c2.word_id)
      JOIN (SELECT user_id, count(*) AS word_count
            FROM connections
            GROUP BY user_id) u1 ON (c1.user_id = u1.user_id)
      JOIN (SELECT user_id, count(*) AS word_count
            FROM connections
            GROUP BY user_id) u2 ON (c2.user_id = u2.user_id)
) AS shared_words
GROUP BY user1_id, user2_id, user1_wc, user2_wc;

为了清楚起见,Friendship_suggestion在SELECT上,你可能需要按它进行过滤,所以你可能只是将它移动到HAVING子句。

答案 1 :(得分:2)

我将此选项投入到您的查询考虑中...来自查询的第一部分是什么都不做,只是让您正在考虑的一个用户作为查找所有其他常用词的基础。 where子句适用于该用户(别名结果为OnePerson)。

然后,添加到from子句(WITHOUT A JOIN),因为OnePerson记录将始终是单个记录,我们希望它的总字数可用,但实际上并没有看到如果另一个是100到30威力人只有10个单词匹配3 ...我实际上认为它的膨胀和不必要的,你将在后面的PreQuery中看到。

因此,下一个表是连接表(别名为c2),对于正在考虑的每个“其他”人来说,这是正常的INNER JOIN到单词表。

然后,再次将此c2再次连接到连接表,基于公共单词Id-AND OiasWords,并且OnesWords用户ID是要与之比较的主user_id的用户ID。此OnesWords别名与单词表连接,因此如果与主要人员匹配,我们可以将该“常用单词”作为group_concat()的一部分。

所以,现在我们抓住原始单人的总词数(仍然不是你需要它),对另一个人的所有单词的计数,以及所有单词的计数(通过总和/大小的时间) COMMON与原始人员按“其他”用户ID分组。这样就可以获得所有结果并将其作为别名“PreQuery”。

现在,我们可以将其加入到用户的表中以获取名称和头像以及相应的计数和常用单词,但是将基于每个“其他用户”可用单词总数的WHERE子句应用于“in”常见的“与第一个人的话(见......我认为你不需要原始查询/计数作为百分比考虑的基础)。

SELECT
      u.name_surname, 
      u.avatar, 
      PreQuery.*
   from
      ( SELECT
              c2.user_id, 
              One.TotalWords, 
              COUNT(*) as OtherUserWords,
              GROUP_CONCAT(words_en.word) AS InCommonWords,
              SUM( case when OnesWords.word_id IS NULL then 0 else 1 end ) as InCommonWithOne
           from
              ( SELECT c1.user_id, 
                       COUNT(*) AS TotalWords
                    from
                       `connections` c1
                    where
                       c1.user_id = :PrimaryPersonBasis ) OnePerson,
              `connections` c2
                 LEFT JOIN `connections` OnesWords
                    ON c2.word_id = OnesWords.word_id
                    AND OnesWords.user_id = OnePerson.User_ID
                    LEFT JOIN words_en
                       ON OnesWords.word_id = words_en.id
           where
              c2.user_id <> OnePerson.User_ID
           group by
              c2.user_id ) PreQuery
         JOIN users u
            ON PreQuery.user_id = u.id   
   where
      PreQuery.OtherUserWords * :nPercentToConsider >= PreQuery.InCommonWithOne
   order by
      PreQuery.InCommonWithOne DESC,
      u.name_surname 

这是一个修订的WITHOUT然后需要预先查询第一个人的原始单词。

SELECT
      u.name_surname, 
      u.avatar, 
      PreQuery.*
   from
      ( SELECT
              c2.user_id, 
              COUNT(*) as OtherUserWords,
              GROUP_CONCAT(words_en.word) AS InCommonWords,
              SUM( case when OnesWords.word_id IS NULL then 0 else 1 end ) as InCommonWithOne
           from
              `connections` c2
                 LEFT JOIN `connections` OnesWords
                    ON c2.word_id = OnesWords.word_id
                    AND OnesWords.user_id = :PrimaryPersonBasis
                    LEFT JOIN words_en
                       ON OnesWords.word_id = words_en.id
           where
              c2.user_id <> :PrimaryPersonBasis
           group by
              c2.user_id 
           having 
              COUNT(*) * :nPercentToConsider >= 
                SUM( case when OnesWords.word_id IS NULL then 0 else 1 end ) ) PreQuery
         JOIN users u
            ON PreQuery.user_id = u.id   
   order by
      PreQuery.InCommonWithOne DESC,
      u.name_surname 

查询可能会有一些调整,但是您的原始查询让我相信您可以轻松找到简单的内容,例如别名或字段名称类型-o实例。

另一个选项可能是预先查询所有用户以及他们拥有UP FRONT的相应单词数量,然后使用主要人物的单词与其他任何人明确地比较这些常用单词...这可能更有效,因为多个联接会在较小的结果集上更好。如果您有10,000个用户并且用户A有30个单词,并且只有500个其他用户拥有一个或多个这些单词,那么该怎么办...为什么要与所有10,000个进行比较...但是如果预先拥有每个用户的简单摘要什么应该是一个几乎即时的查询基础。

SELECT
      u.name_surname, 
      u.avatar, 
      PreQuery.*
   from 
      ( SELECT
              OtherUser.User_ID,
              AllUsers.EachUserWords,
              COUNT(*) as CommonWordsCount,
              group_concat( words_en.word ) as InCommonWords
           from
              `connections` OneUser
                 JOIN words_en
                    ON OneUser.word_id = words_en.id
                 JOIN `connections` OtherUser
                    ON OneUser.word_id = OtherUser.word_id
                    AND OneUser.user_id <> OtherUser.user_id
                    JOIN ( SELECT
                                 c1.user_id, 
                                 COUNT(*) as EachUserWords
                              from
                                 `connections` c1
                              group by
                                 c1.user_id ) AllUsers
                      ON OtherUser.user_id = AllUsers.User_ID
           where
              OneUser.user_id = :nPrimaryUserToConsider
           group by
              OtherUser.User_id,
              AllUsers.EachUserWords ) as PreQuery
      JOIN users u
         ON PreQuery.uer_id = u.id
   where
      PreQuery.EachUserWords * :nPercentToConsider >= PreQuery.CommonWordCount
   order by
      PreQuery.CommonWordCount DESC,
      u.name_surname 

答案 2 :(得分:1)

我可以建议一种不同的方式来看待你的问题吗?

您可以查看相似性指标,例如Cosine Similarity,它可以让您更好地衡量用户之间基于单词的相似度。要了解您的情况,请考虑以下示例。您有一个用户A = {house, car, burger, sun}的单词u1和用户B = {flat, car, pizza, burger, cloud}的另一个向量u2

鉴于这些单独的向量,您首先构建另一个将它们放在一起的向量,这样您就可以向每个用户映射他/她是否在其向量中包含该单词。像这样:

| -- | house | car | burger | sun | flat | pizza | cloud |
----------------------------------------------------------
| A  |  1    |  1  |   1    |  1  |  0   |   0   |   0   |
----------------------------------------------------------
| B  |  0    |  1  |   1    |  0  |  1   |   1   |   1   |
----------------------------------------------------------

现在每个用户都有一个向量,其中每个位置对应于每个用户的每个单词的值。这里它代表一个简单的计数,但如果适用于您的情况,您可以使用基于字频的不同指标来改进它。看看最常见的一个,名为tf-idf

拥有这两个向量,您可以按如下方式计算它们之间的cosine similarity

这主要是计算上面向量的每个位置之间的乘积之和除以它们的相应幅度。在我们的例子中,即0.47,在0到1之间变化的范围内,两个向量的相似性越高。

如果选择这样,则无需在数据库中进行此计算。您可以计算代码中的相似性,并将结果保存在数据库中。有几个库可以为您做到这一点。在Python中,看一下numpy library。在Java中,请查看Weka和/或Apache Lucene