我的问题是如何优化数据库的体系结构及其上的请求,以提高集合包含查询的性能。
我有一个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倍。我不知道在添加或删除区域时维持更新此表的重要性是多么重要,但我确信与我尝试过的其他解决方案相比,这是值得的。
但是,再一次,也许有更好的解决方案?
答案 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毫秒内执行,我认为不需要更改它。