【问题标题】:Self join between subqueries for duplicate detection用于重复检测的子查询之间的自连接
【发布时间】:2017-09-20 21:43:43
【问题描述】:

我在对子查询执行自联接的查询时遇到了一些问题,它花费的时间比我想象的要多,而且我在理解原因时遇到了一些问题。

问题如下,所有者可以拥有物品,但某些物品可能会出现两次,属于不同的所有者,从每个所有者我们可能会得到关于物品的稍微不同的信息,或者某些字段可能为空。

这是我的数据库的一个简单版本,它不包含 FK,并且索引仅在 IdOwner、IdItem 和 IdCategry 出现时存在。

所有者:

+----------------+---------------+------+-----+
| Field          | Type          | Null | Key | 
+----------------+---------------+------+-----+
| IdOwner        | bigint(20)    | NO   | PRI |
| IdPlace        | int(10)       | NO   |     |
| SomeDate       | datetime      | YES  |     |
+----------------+---------------+------+-----+

项目:

+----------------+---------------+------+-----+
| Field          | Type          | Null | Key | 
+----------------+---------------+------+-----+
| IdItem         | bigint(20)    | NO   | PRI |
| IdOwner        | bigint(20)    | NO   | MUL |
| IdCategory     | int(10)       | NO   |     |
| DupValue1      | varchar()     | YES  |     |
      .
      .
      .
| DupValueN      | varchar()     | YES  |     |
+----------------+---------------+------+-----+

国家:

+----------------+---------------+------+-----+
| Field          | Type          | Null | Key | 
+----------------+---------------+------+-----+
| IdOwner        | bigint(20)    | NO   | PRI |
| Country        | Varchar()     | NO   | PRI |
+----------------+---------------+------+-----+

DupValues 1 到 N 是我发现当项目重复时最有可能相同的列。

这是我目前正在使用的查询的简化版本:

SELECT subquery1.IdItem, subquery2.IdItem FROM 
(SELECT i1.IdCategory, i1.IdOwner, i1.IdItem, i1.DupValue1, o1.IdSite, o1.SomeDate, COUNTRY.country
FROM ITEMS i1 
LEFT JOIN OWNER o1 ON o1.IdOwner=i1.IdOwner 
LEFT JOIN COUNTRY ON i1.IdOwner=COUNTRY.IdOwner
WHERE i1.IdOwner>9000000) 
as subquery1
INNER JOIN 
(SELECT i2.IdCategory, i2.IdOwner, i2.IdItem, i2.DupValue1, o2.IdSite, o2.SomeDate, COUNTRY.country
FROM ITEMS i2 
LEFT JOIN COUNTRY COUNTRY ON i2.IdOwner=COUNTRY.IdOwner
LEFT JOIN OWNER o2 ON o2.IdOwner=i2.IdOwner 
WHERE i2.IdOwner>9000000) 
as subquery2
ON subquery1.IdItem<subquery2.IdItem 
AND subquery1.IdCategory=subquery2.IdCategory 
AND subquery1.IdSite!=subquery2.IdSite AND subquery1.country=subquery2.country 
AND DATE(subquery1.SomeDate)=DATE(subquery2.SomeDate) 
AND (subquery1.DupValue1=subquery2.DupValue1 OR subquery1.DupValue1 IS NULL OR subquery2.DupValue1 IS NULL) 

还有更多的 SupValue 都具有相同的格式。

WHERE 子句用于限制所有者的数量,因为我仍在测试查询,当 WHERE 子句到位时,它将所有者限制为约 700k 行,并且使用该行数,wuery 需要约 30 分钟过程。

当我在查询中使用解释时,我得到这个:

+------+-------------+---------+--------+----------------------------------------+-------------+---------+------------------------+-------+------------------------------------+
| id   | select_type | table   | type   | possible_keys                          | key         | key_len | ref                    | rows  | Extra                              |
+------+-------------+---------+--------+----------------------------------------+-------------+---------+------------------------+-------+------------------------------------+
|    1 | SIMPLE      | i1      | range  | PRIMARY,UnivocID,dg_owner,dg_category  | UnivocID    | 8       | NULL                   | 19056 | Using index condition              |
|    1 | SIMPLE      | o1      | eq_ref | PRIMARY                                | PRIMARY     | 8       | i1.IdTender            |     1 |                                    |
|    1 | SIMPLE      | country | ref    | PRIMARY                                | PRIMARY     | 8       | i1.IdTender            |     1 | Using index                        |
|    1 | SIMPLE      | i2      | ref    | PRIMARY,UnivocID,dg_owner,dg_category  | dg_category | 4       | i1.IdMolecule          |   657 | Using index condition; Using where |
|    1 | SIMPLE      | o2      | eq_ref | PRIMARY                                | PRIMARY     | 8       | i2.IdTender            |     1 | Using where                        |
|    1 | SIMPLE      | country | ref    | PRIMARY                                | PRIMARY     | 8       | i2.IdTender            |     1 | Using index                        |
+------+-------------+---------+--------+----------------------------------------+-------------+---------+------------------------+-------+------------------------------------+

MariaDB 版本:10.1

我的两个问题:

¿是subquery2subquery1 的每一行都执行,这就是导致执行时间如此长的原因,还是ON 子句的性质有问题?

¿查询可以改进吗,也许放弃 JOIN 以支持或其他运算符?

【问题讨论】:

  • 您是否需要子查询,或者可以在没有子查询的情况下直接完成(如果您在完整查询中包含聚合函数,则可能需要子查询)。
  • @Kickstart 我需要子查询,因为我需要将 3 个表中的数据转换为 1,然后我需要对结果表进行操作以检测重复项,进行内部连接是我知道的唯一方法这样做。
  • 可能的问题是您使用的是 LEFT OUTER JOIN,因此一些返回的字段可能是 NULL。并且 NULL 不等于 NULL(但您基于国家字段相等而加入)。如果影响您的子查询具有 LEFT OUTER JOINs 的开销,但结果会以一种方式处理,这意味着结果可能来自 INNER JOINs
  • @Kickstart 一个项目“总是”有一个所有者,一个所有者总是有一个国家,所以永远不应该有一个 NULL 国家,因为我知道可以为空的字段我使用最后一行查询。
  • 在这种情况下切换到在子查询中使用 INNER JOIN。但这也应该意味着您不需要使用子查询。

标签: mysql join mariadb


【解决方案1】:

由于没有表格布局或测试数据,我无法对此进行测试(您还可以在 join 子句中引用名为 SubmissionDate 的列,但该字段不会从子查询中带回),但以下内容应避免使用子查询。希望它能更好地使用索引:-

SELECT subquery1.IdItem, subquery2.IdItem 
FROM ITEMS i1 
INNER JOIN ITEMS i2 
ON i1.IdItem < i1.IdItem AND i1.IdCategory = i1.IdCategory AND (i1.DupValue1 = i2.DupValue1 OR i1.DupValue1 IS NULL OR i2.DupValue1 IS NULL)
INNER JOIN OWNER o1 ON o1.IdOwner = i1.IdOwner 
INNER JOIN COUNTRY c1 ON i1.IdOwner = c1.IdOwner
INNER JOIN OWNER o2 ON o2.IdOwner = i2.IdOwner AND o1.IdSite != o2.IdSite
INNER JOIN COUNTRY c2 ON i2.IdOwner = c2.IdOwner AND c1.country = c2.country 
WHERE i1.IdOwner > 9000000
AND i2.IdOwner > 9000000

【讨论】:

  • 这不起作用,它需要更多时间,我认为这是因为在连接末尾应用了 WHERE 子句,所以它只修剪输出。 “并且您还参考了联接子句中名为 SubmissionDate 的列”。我的错,它已经修复了。
  • 如果您可以发布表格布局和一些示例数据,我可以尝试一下。但是您当前的查询将受到影响,因为 MySQL 可能难以从子查询中提取任何索引以进行连接。如果您只从每个子查询中返回一小部分数据,则可能会更快,但可能不会返回大量数据。
【解决方案2】:

另一种调查或处理重复数据的技术是将每个表中的行聚集到一个表中,然后在该表上执行GROUP BY

CREATE TEMPORARY TABLE t
    ( SELECT stuff from one table or set of tables )
    UNION ALL
    ( SELECT stuff from the other table or tables )
;
SELECT * FROM t
    GROUP BY IdOwner, IdSite, country
;

如果需要,在“stuff”中添加一个额外的列以区分来源:

SELECT 1 AS source, ...

性能不佳的原因:

FROM ( subquery1 )
JOIN ( subquery2 ) ON ...

没有可以执行ON 的索引(至少到5.6)。因此子查询结果被完全扫描。即使是 5.6,创建索引也有一些开销。

另一个提示,回复:AND DATE(subquery1.SomeDate)=DATE(subquery2.SomeDate):在构建子查询时计算 DATE(SomeDate) - 这使它成为一个 1 次过程,而不是在执行子表扫描时重复的过程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-18
    相关资源
    最近更新 更多