如何以更快的方式计算1:N:N关系中的行?

时间:2018-11-20 06:23:18

标签: mysql sql

这个问题对我来说有点复杂,我无法用一句话来解释它,因此标题似乎很含糊。

我的MySQL数据库中有3个表,其结构如下所示:

  1. word_list(500万行)

    +-----+--------+
    | wid | word   |
    +-----+--------+
    |   1 | foo    |
    |   2 | bar    |
    |   3 | hello  |
    +-----+--------+

  1. paper_word_relation(一千万行)

    +-----+-------+
    | pid | word  | 
    +-----+-------+
    |   1 |    1  | 
    |   1 |    2  | 
    |   1 |    3  | 
    |   2 |    1  | 
    |   2 |    3  | 
    +-----+-------+

  1. paper_citation_relation(8万行)

    +----------+--------+
    | pid_from | pid_to | 
    +----------+--------+
    |        1 |     2  | 
    |        1 |     3  | 
    |        1 |     4  |
    |        2 |     1  |
    |        2 |     3  |
    +----------+--------+

我想找出多少论文包含单词W,并列举论文也包含单词W(对于列表中的每个单词)

我使用两个内部联接来完成这项工作,但是当单词流行时-50s以上似乎非常慢(如果单词很少使用则非常快-0.1s以下),这是我的代码


    SELECT COUNT(*) FROM (
    SELECT a.pid_from, a.pid_to, b.word FROM paper_citation_relation AS a 
    INNER JOIN paper_word_relation AS b ON a.pid_from = b.pid
    INNER JOIN paper_word_relation AS c ON a.pid_to = c.pid
    WHERE b.word = 2 AND c.word = 2) AS d

如何更快地执行此操作?我的查询效率不够吗?还是有关数据量的问题?

我只能提出一种解决方案,即删除paper_word_relation表中少于2个单词。 (大约400万个单词仅出现一次)

谢谢!

2 个答案:

答案 0 :(得分:1)

如果仅考虑获取计数,则不应该首先将结果获取到派生表中,然后将行计数出去。这个可以创建不必要的临时表,以在内存中存储大量数据。您可以直接计算行数。

我还认为您需要计算唯一数量的论文。由于paper_citation_relation表中的多对多关系,单张纸可能会出现重复的行

SELECT COUNT(DISTINCT a.pid_from) 
FROM paper_citation_relation AS a 
INNER JOIN paper_word_relation AS b ON a.pid_from = b.pid
INNER JOIN paper_word_relation AS c ON a.pid_to = c.pid
WHERE b.word = 2 AND c.word = 2

为了提高性能,您将需要进行以下索引编制:

  • (pid_from, pid_to)表中paper_citation_relation上的综合索引。
  • (pid, word)表中paper_word_relation上的综合索引。

我们可能还可能通过减少一个联接并在AND/OR中使用基于条件HAVING的过滤来进一步优化查询。不过,您将需要对其进行基准测试。

SELECT COUNT(*) 
FROM (
      SELECT a.pid_from  
      FROM paper_citation_relation AS a 
      INNER JOIN paper_word_relation AS b 
        ON (a.pid_from = b.pid OR 
            a.pid_to = b.pid)  
      GROUP BY a.pid_from 
      HAVING SUM(a.pid_from = b.pid AND b.word = 2) AND 
             SUM(a.pid_to = b.pid AND b.word = 2)
     )

答案 1 :(得分:0)

在第一个1:n联接之后,您将多次获得相同的pid_to,而下一个联接不再是1:n而是n:m,从而在联接之前创建了可能巨大的中间结果最后DISTINCT。它与CROSS JOIN类似,但流行词(例如10 * 10与1000 * 1000行。

您必须在加入之前删除重复项,这应该返回与@MadhurBhaiya的答案相同的数字

SELECT Count(*) -- no more DISTINCT needed
FROM 
 (
    SELECT DISTINCT cr.pid_to -- reducing m to 1
    FROM paper_citation_relation AS cr
    JOIN paper_word_relation AS wr 
      ON cr.pid_from = wr.pid
    WHERE wr.word = 2
 ) AS dt
JOIN paper_word_relation AS wr
  ON dt.pid_to = wr.pid  -- 1:n join again
WHERE wr.word = 2

如果要计算被引用的论文数,则需要首先从pid获得pid_frompid_topaper_citation_relation)的不同列表然后加入特定的单词。

SELECT Count(*)
FROM
( -- get a unique list of cited or citing papers
    SELECT pid_from AS pid -- citing
    FROM paper_citation_relation
    UNION -- DISTINCT by default
    SELECT pid_to          -- cited
    FROM paper_citation_relation 
) AS dt
JOIN paper_word_relation AS wr
  ON wr.pid = dt.pid
WHERE wr.word = 2 -- now check for the searched word

由此返回的数字可能会稍高(无论引用还是引用,它都计入论文)。