【问题标题】:SQL Alternative to performing an INNER JOIN on a single tableSQL 对单个表执行 INNER JOIN 的替代方法
【发布时间】:2009-08-07 20:56:45
【问题描述】:

我有一个大表 (TokenFrequency),其中有数百万行。 TokenFrequency 表的结构如下:

表 - 令牌频率

  • id - int,主键
  • 来源 - 整数、外键
  • 令牌 - 字符
  • count - int

我的目标是选择其中两个来源具有相同标记的所有行。例如,如果我的表格如下所示:

id --- 来源 --- 令牌 --- 计数
1 ------ 1 --------- 狗 ------- 1
2 ------ 2 --------- 猫 -------- 2
3 ------ 3 --------- 猫 -------- 2
4 ------ 4 --------- 猪 -------- 5
5 ------ 5 --------- 动物园 ------- 1
6 ------ 5 --------- 猫 -------- 1
7 ------ 5 --------- 猪 -------- 1

我想要一个 SQL 查询来给我源 1、源 2 和计数的总和。例如:

source1 --- source2 --- token --- 计数
---- 2 ----------- 3 --------- 猫 -------- 4
---- 2 ----------- 5 --------- 猫 -------- 3
---- 3 ----------- 5 --------- 猫 -------- 3
---- 4 ----------- 5 --------- 猪 -------- 6

我的查询如下所示:

SELECT  F.source AS source1, S.source AS source2, F.token, 
       (F.count + S.count) AS sum 
FROM       TokenFrequency F 
INNER JOIN TokenFrequency S ON F.token = S.token 
WHERE F.source <> S.source

这个查询工作正常,但我遇到的问题是:

  1. 我有一个包含数百万行的 TokenFrequency 表,因此需要更快的替代方法来获得此结果。
  2. 我当前的查询是重复的。例如它的选择:
    source1=2,source2=3,token=cat,count=4
    source1=3,source2=2,token=cat,count=4
    这不是什么大问题,但如果有办法消除这些问题并反过来提高速度,那将非常有用

我遇到的主要问题是查询速度,我当前的查询需要几个小时才能完成。我认为自己的表上的 INNER JOIN 是问题所在。我确信必须有一种方法来消除内部连接并仅使用 TokenFrequency 表的一个实例来获得类似的结果。我提到的第二个问题也可能会促进查询速度的提高。

我需要一种方法来重组此查询,以便以更快、更有效的方式提供相同的结果。

谢谢。

【问题讨论】:

  • 您能否发布查询的解释(dev.mysql.com/doc/refman/5.0/en/explain.html)。它将帮助人们了解他们如何帮助您进行优化。
  • 你需要提供一些索引信息,哪些列等
  • 这是我最初发布的查询的解释。 id:1, select_type:SIMPLE, table:F & S, type:ALL, Possible_keys:NULL, Key:NULL, Key_len:NULL, ref:NULL, rows:8, Extra: Using where;使用连接缓冲区有两行返回,唯一的区别是两个表名F和S。
  • 在您的问题中发布解释,以便获得正确的格式。但是从外观上看,您加入的列似乎没有索引?

标签: sql mysql performance inner-join


【解决方案1】:

我需要更多信息来诊断速度问题,但要删除 dups,请将其添加到 WHERE:

AND F.source<S.source

【讨论】:

  • 啊,这么简单。这非常适合消除重复项。谢谢
【解决方案2】:

试试这个:

SELECT token, GROUP_CONCAT(source), SUM(count)
FROM TokenFrequency
GROUP BY token;

这应该会运行得更快,并且还可以消除重复。但源将以逗号分隔的列表形式返回,因此您必须在应用程序中展开该列表。

您也可以尝试在列 token, source, count(按此顺序)上创建复合索引,并使用 EXPLAIN 进行分析,以查看 MySQL 是否足够聪明,可以将其用作此查询的 covering index


更新:我似乎误解了你的问题。您不想要每个令牌的计数总和,您想要给定令牌的每对源的计数总和。

我相信内部连接是最好的解决方案。 SQL 的一个重要准则是,如果您需要针对两个不同的行计算一个表达式,那么您需要进行连接。

但是,我上面提到的一种优化技术是使用覆盖索引,以便您需要的所有列都包含在索引数据结构中。好处是您的所有查找都是 O(log n),并且查询不需要执行第二次 I/O 来读取物理行来获取其他列。

在这种情况下,您应该在列 token, source, count 上创建覆盖索引,正如我上面提到的。还要尽量分配足够的缓存空间,以便索引可以缓存在内存中。

【讨论】:

  • +1 表示正确的方法;但是这样的索引几乎和整个记录一样大,您认为它会比仅在令牌上索引更快吗?
  • 取决于行数和其他系统特定因素。唯一可以确定的方法是使用 您的 数据库进行尝试并测量性能。
  • 这是一种很好的方法,但如果您有一个位于多个来源中的令牌,那么它会产生的唯一问题是您将所有这些案例加在一起。例如,在我的示例中,令牌“cat”在源 2,3 和 5 中,因此它给我的计数为 5,而不是给我计数为 4 的 2&3、计数为 3 的 3&5 和计数为 3 的 2&5计数为 3。在我真实的大量数据中,几乎每个文档中都出现了一些标记,这将为我提供数千个来源的 GROUP_CONCAT 及其尊重计数。
  • 对您的问题的误解表示歉意。请参阅上面的更新。
  • 感谢您的更新和提示。我将使用转换索引并更新我的结果。
【解决方案3】:

如果令牌没有被索引,它当然应该被索引。

【讨论】:

    猜你喜欢
    • 2019-12-05
    • 1970-01-01
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多