优化集合包含查询

时间:2014-09-11 20:16:22

标签: php mysql sql request query-optimization

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

我有一个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

Link_table

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倍。我不知道在添加或删除区域时维持更新此表的重要性是多么重要,但我确信与我尝试过的其他解决方案相比,这是值得的。

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

1 个答案:

答案 0 :(得分: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毫秒内执行,我认为不需要更改它。