优化集合包含查询


Optimising a set inclusion query

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

我有一个PHP/MySQLWeb应用程序,包括一个包含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和四)中。

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

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

到目前为止,我的解决方案是通过mySQL查询所有领土及其相应国家的列表,在PHP中循环浏览该列表,并将国家列表中包含的领土(或其他函数包含的领土)保留在我的参考领土中。

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

我试着构建一个查询来获得结果,但到目前为止,没有什么能比得上我的第一个系统。

编辑:KIKO软件提供了一个解决方案,可以在一个请求中得到答案。在尝试了它并将性能与我迄今为止使用的函数进行比较后,它比我的函数慢两倍。这个结果让我很惊讶,但我做了足够多的测试来确定。

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

包容性指数

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倍。我还不知道无论何时添加或删除某个区域,保持更新此表的通行费有多重要,但我确信与我尝试的其他解决方案相比,这是值得的。

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

感谢您的数据库。我从下载了它

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毫秒内执行,我认为没有必要更改它