优化集合包含查询

Posted

技术标签:

【中文标题】优化集合包含查询【英文标题】:Optimising a set inclusion query 【发布时间】:2014-09-11 20:16:21 【问题描述】:

我的问题是关于如何优化数据库的架构及其上的请求,以提高集合包含查询的性能。

我有一个 php/mysql 网络应用程序,包括一个带有 3 个表的数据库:

国家

id         name
-----------------------
1          Country 1
2          Country 2
3          Country 3
4          Country 4

领土

id         name
--------------------------------------------------
1          Territory made of countries 1 and 2
2          Territory made of country 1
3          Territory made of country 3
4          Territory made of countries 1, 3 and 4
5          Territory made of countries 1, 2, 3 and 4

链接表

terr_id        country_id
---------------------------
1              1
1              2
2              1
3              3
4              1
4              3
4              4
5              1
5              2
5              3
5              4

我的应用程序经常需要知道哪些地区包含在哪些地区。 在上面的示例中,我们看到地区 #2(国家 1)和 #3(国家 3)包含在地区 #4(国家 1、3 和 4)以及 #5(国家 1、2、3 和4).

我需要一个函数来列出给定区域中包含的所有区域(例如,#5 中包含的所有区域都是 #5、#4、#3、#2),以及列出所有包含给定区域的区域(例如,包含 #1 的所有区域都是 #1 和 #5)。两个不同的函数,返回一种对称的结果。

构建此类功能的最佳方法是什么?

到目前为止,我的解决方案是通过 mySQL 查询所有地区及其对应国家/地区的列表,在 PHP 中循环遍历此列表,并在我的领土中保留包含国家列表(或包含其他功能)的地区参考。

我编写的函数很可能不够高效。此外,它们在我的系统中被调用了数百次,因此在这种情况下,几毫秒的增益意味着很多。

我尝试构建一个查询来获取结果,但到目前为止,没有什么比我的第一个系统更好。

编辑: KIKO Software provided a solution 在一个请求中获得答案。 在尝试并与我目前使用的函数比较性能后,它比我的函数慢了两倍。这个结果让我感到惊讶,但我做了足够多的测试来确定。

我刚刚尝试了第三种选择,即创建另一个表来索引区域之间的包含:

inclusion_index

terr_id_ref        terr_id_child
---------------------------
1              1
1              2
2              2
3              3
4              2
4              3
4              4
5              1
5              2
5              3
5              4
5              5

因此,请求一个区域中包含的所有区域只需要以下请求:

SELECT terr_id_child
FROM inclusion_index
WHERE terr_id_ref = 5

不出所料,这个系统实际上比我之前的尝试快 100 倍。我还不能说在添加或删除区域时保持更新此表的费用有多重要,但与我尝试过的其他解决方案相比,我很确定这是值得的。

但再一次,也许有更好的解决方案?

【问题讨论】:

您说,“在上面的示例中,我们看到区域 #2 和 #3 包含在区域 #4 和区域 #5 中。” 它没有在我看来不要那样。地区 #2 和 #3 似乎只包含在地区 #5 中。 请确保您的问题逻辑合理。您似乎要求两个相同的功能,而您自己的示例对我来说似乎不正确。 【参考方案1】:

感谢数据库。我是从以下网站下载的:

https://drive.google.com/file/d/0B9G-5dTlZuDpdkt4U2QwR1RwRlE/edit?usp=sharing

并重新创建了您的表格。我现在已经能够测试 SQL 命令,这使得创建正确的 SQL 命令变得容易得多。

这次我坚持使用子查询,但我将它们拆分为更简单的步骤,因此它们更易于理解。我选择了 id = 1602 的地区作为我的目标。那是“主要欧洲”。

第 1 步:查找选定地区的所有国家/地区

SELECT country_id 
FROM link_table 
WHERE terr_id = 1602

这导致了这个集合:

id      name
5       Germany
17      Austria
69      Spain
77      France
83      Gibraltar
110     Italy
135     Malta
183     United Kingdom
192     Saint Helena

这是一个奇怪的集合,但考虑到所涉及的 SQL 和表的简单性,我认为它不会错。

第 2 步:找出所有不在第 1 步结果集中的国家

SELECT id 
FROM countries 
WHERE id NOT IN (SELECT country_id 
                 FROM link_table 
                 WHERE terr_id = 1602)

再一次,这很简单,它一定是正确的。这是一个大集合。现在我们知道,包含任何这些国家的任何领土都不会包含在“主要欧洲”的领土内。要到达那里,我们首先必须采取另一个中间步骤:

第 3 步:在第 2 步的结果集中查找具有多个国家/地区之一的所有地区

SELECT DISTINCT terr_id 
FROM link_table 
WHERE country_id IN (SELECT id 
                     FROM countries 
                     WHERE id NOT IN (SELECT country_id 
                                      FROM link_table 
                                      WHERE terr_id = 1602))

这些都是我们不想要的领土。所以最后一步现在很容易:

第 4 步:找出所有不在第 3 步结果集中的地区

SELECT * 
FROM territories 
WHERE id NOT IN (SELECT DISTINCT terr_id 
                 FROM link_table 
                 WHERE country_id IN (SELECT id 
                                      FROM countries 
                                      WHERE id NOT IN (SELECT country_id 
                                                       FROM link_table 
                                                       WHERE terr_id = 1602)))

现在这几乎可行,但我发现很多没有国家的地区都包含在最终结果中。所以我们需要过滤掉那些:

第 5 步:过滤掉所有没有国家/地区的地区

SELECT * 
FROM territories 
WHERE EXISTS (SELECT * 
              FROM link_table 
              WHERE terr_id = id) AND
      id NOT IN (SELECT DISTINCT terr_id 
                 FROM link_table 
                 WHERE country_id IN (SELECT id 
                                      FROM countries 
                                      WHERE id NOT IN (SELECT country_id 
                                                       FROM link_table 
                                                       WHERE terr_id = 1602)))

结果集现在是:

32      France
384     Germany
387     United Kingdom
392     Spain
397     Italy
417     Austria
538     United Kingdom
546     Germany, Austria
627     Spain, France
714     United Kingdom
719     Malta
747     Italy, United Kingdom
1328    Gibraltar, Malta, Saint Helena
1398    France, United Kingdom
1399    Germany, United Kingdom
1402    Germany, France
1602    MAIN EUROPE
1626    Saint Helena
1690    Germany, France, United Kingdom
1720    United Kingdom
1768    Germany, Austria, Italy
1883    France, Gibraltar, Malta, United Kingdom, Saint He...
1885    France, Gibraltar, Malta, Saint Helena
1959    Spain, Italy
1968    France, Italy

这不是我写过的最好的 SQL 命令,但我认为它相当容易理解。可能有更高效的变体,但由于它在我的服务器上执行的时间为 20 毫秒,因此我认为不需要更改它。

【讨论】:

感谢 Kiko,这很有趣,虽然它还不能工作。首先,我必须将...C1.id = (SELECT ... 更改为...C1.id IN (SELECT ...,因为子查询返回国家列表。这个结果的问题是我得到了一个包含我:selectedTerritory 的至少一个国家的所有国家的列表。但没有什么能阻止这些地区拥有其他未包含在:selectedTerritory 中的地区。 你的小数据库有导出吗?在网上任何地方放一个链接。这节省了我自己制作的时间,并且我可以真正测试 SQL。 给你:drive.google.com/file/d/0B9G-5dTlZuDpdkt4U2QwR1RwRlE/… 谢谢,我去试试。您可能需要两个连续的 SQL 命令才能将领土完全包含在给定的命令中。 我完全重写了答案。

以上是关于优化集合包含查询的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis resultMap 嵌套集合

Hibernate查询优化——类级别查询(集合策略)

详解SQL语句的集合运算

Oracle中的优化问题

包含集合值属性的条件查询

输入参数集合包含 null 的 JPQL 查询