关于postgresql二次分组和多个表格进行外连接的问题


昨天写postgresql代码的时候遇到一个棘手的问题,题目是要计算在各个城市使用次数最多的手机型号。此时只有一个表,叫做usage,里面存储了每一次用户使用服务时的手机型号以及使用的城市,即,usage里有phone_id和城市的zip_code。

此时要统计每个城市使用次数最多的手机型号,自然而然地想到先进行一次GROUP BY,于是写出如下代码

SELECT u.zip_code,u.phone_id,COUNT(*) number
FROM usage u
GROUP BY u.zip_code,u.phone_id;

此时发现出来的结果是:

zip_code | phone_id | number
----------+----------+--------
888 | 22222 | 2
444 | 11111 | 2
444 | 44444 | 1
888 | 33333 | 3
444 | 99999 | 1
222 | 44444 | 4
888 | 11111 | 8
444 | 00000 | 1
444 | 22222 | 6
444 | 55555 | 2
(10 行记录)

这个时候发现只分一次组是不够的,这样只统计出了在某个城市使用某个手机型号的次数,但是题目要求要算出每个城市使用次数最多的手机型号,所以需要二次分组。于是,我尝试着写出了如下代码

SELECT pc.zip_code,pc.phone_id,MAX(pc.number)
FROM (
	SELECT u.zip_code,COUNT(*) number
	FROM usage u
	GROUP BY u.zip_code,u.phone_id
)pc
GROUP BY pc.zip_code;
这个时候问题来了,报错了:

错误: 字段 "pc.phone_id" 必须出现在 GROUP BY 子句中或者在聚合函数中使用
第1行SELECT pc.zip_code,MAX(pc.number)

在GROUP BY中没出现的字段,无法被SELECT出来,那么我在GROUP BY中加上pc.phone_id字段?不行,这样一分组,就又变成了第一次分组的效果了!然后我开始想,能不能在第一次分组中就使用两次的聚合函数——MAX(COUNT(*))?试了一下,果然报错。突然脑洞一开就觉得sql该改进这一部分的功能,这样很不合理,之所以不能使用两次聚合函数的本质是行数不匹配,但是我用两组信息分组,针对第一组信息的二次聚合函数就应该可以使用才对的!墙裂要求sql开发人员在下一版本中增加功能!然后我又尝试在WHERE中使用pc.number = MAX(pc.number),但是可惜的是,where中不允许出现聚合函数,想想也有道理,where是在GROUP BY之前执行的,如果能让你使用了,那岂不是乱套了?但是题目要求啊!怎么办!我就这样纠结了一个晚上,直到睡觉了也没想出来,不开心地睡下了。第二天早上起来接着想!想啊想,题目要求只能使用一个view就把一切信息全部都整出来,那么肯定是利用子查询,那么子查询可以出现的位置都有哪些地方呢?SELECT后面?不对!FROM后面?使用过了啊!WHERE里?对!WHERE里使用子查询!试试看!于是我先编写了一个查询以供使用:

SELECT pc.zip_code,u.phone_id
)pc
GROUP BY pc.zip_code

放在命令行里验证了一下,正确!

zip_code | max
----------+-----
888 | 8
222 | 4
444 | 6
(3 行记录)

只是缺少phone_id字段而已,没关系,在外部补上!

正确代码如下:

第一部分,没有加外连接版本:


CREATE OR REPLACE VIEW view_most_used_phone_per_county AS
SELECT c.county_name county,p.phone_name phone
FROM (
	SELECT u.zip_code,u.phone_id
)pc JOIN county c ON pc.zip_code = c.zip_code
JOIN phone p ON pc.phone_id = p.phone_id
WHERE (pc.zip_code,pc.number) IN (
	SELECT pc.zip_code,MAX(pc.number)
	FROM (
		SELECT u.zip_code,COUNT(*) number
		FROM usage u
		GROUP BY u.zip_code,u.phone_id
	)pc
	GROUP BY pc.zip_code
)
ORDER BY county;



第二部分,加了外连接:

CREATE OR REPLACE VIEW view_most_used_phone_per_county AS
SELECT c.county_name county,u.phone_id
)pc RIGHT OUTER JOIN county c ON pc.zip_code = c.zip_code
LEFT OUTER JOIN phone p ON pc.phone_id = p.phone_id
WHERE (pc.zip_code,u.phone_id
	)pc
	GROUP BY pc.zip_code
) OR c.zip_code NOT IN (SELECT u.zip_code FROM usage u)
ORDER BY county;



一试,果然成功啦!!

在这里一个小插曲,我中间把

WHERE (pc.zip_code,pc.number) IN (......)

写成了

WHERE (pc.zip_code,pc.number) = (......)
结果一直报错说:“作为一个表达式使用的子查询返回了多列”,然后我就一直想不明白,后来百度了一下,无意中瞄到一个回答提醒了是等号还是IN的问题,这才恍然大悟!



返回多行多列的时候,应该使用IN!记住!!!




好吧,我以为题目被我完美解决了,可是刚才看到了题目中的一句话:若在没有手机在该城市使用,也要显示出来,只不过在右边的phone那一栏显示为空。好了,这个就是外连接的问题了。之前的代码显示zip_code和phone_id,但是题目是要求显示城市名称和手机型号,此时需要JOIN表格county和表格phone。一开始我是这么连接的:

SELECT c.county_name county,u.phone_id
)pc JOIN county c ON pc.zip_code = c.zip_code
JOIN phone p ON pc.phone_id = p.phone_id
............

这样的结果就是,只有有手机使用的城市才会被显示出来。于是我理所当然地改成如下代码,只在county和pc那里加了RIGHT OUTER JOIN:
SELECT c.county_name county,u.phone_id
)pc RIGHT OUTER JOIN county c ON pc.zip_code = c.zip_code
JOIN phone p ON pc.phone_id = p.phone_id
............

但是意外的是,这样出来的结果和之前一样,没有任何变化,咦,怎么回事,我明明外连接了呀?不应该全部出来吗?于是我删掉了phone表格,测试了一下。
SELECT c.county_name county
FROM (
	SELECT u.zip_code,u.phone_id
)pc RIGHT OUTER JOIN county c ON pc.zip_code = c.zip_code
........

这样测试下来是正确的,所有的城市名称显示出来了。于是我又加上了phone,不用JOIN,而改用WHERE限定连接条件,结果还是一样的。挫败的我静静坐下来思考,到底是为什么!

然后我在phone的连接方式上,加上了 LEFT OUTER JOIN:

SELECT c.county_name county,u.phone_id
)pc RIGHT OUTER JOIN county c ON pc.zip_code = c.zip_code
LEFT OUTER JOIN phone p ON pc.phone_id = p.phone_id
.............

奇迹出现了!所有的城市名称都出来了!

这说明什么?在多次JOIN的时候,你以为第在第三个phone表格写的连接方式是和第一个pc表格相连接,所以没有第二个表格county什么事,但实际上,第三个表格是和所有的前两个都相连接了,你要让左边所有的都显示出来,就必须加上LEFT OUTER JOIN,否则就只有符合条件的行被显示出来。具体来讲就是只有符合pc.phone_id = p.phone_id的行被显示出来,就算你在前两个表格中想全部显示出county,可是第三个连接方式中明确指出,只有pc表格里有的phone_id所对应的zip_code才能被显示,也就是说,只有在pc表格里存在有对应手机型号的城市才能被显示出来,所以此时就算在前两个表格中加了RIGHT OUTER JOIN,在第三个连接方式没加LEFT OUTER JOIN,也是白搭!

多个表格进行外连接时,要注意这些约束条件都是相互之间的,而不是说只有邻近的两个表格才有约束力,要注意全部加上外连接符号,并且方向写对才可。

相关文章

项目需要,有个数据需要导入,拿到手一开始以为是mysql,结果...
本文小编为大家详细介绍“怎么查看PostgreSQL数据库中所有表...
错误现象问题原因这是在远程连接时pg_hba.conf文件没有配置正...
因本地资源有限,在公共测试环境搭建了PGsql环境,从数据库本...
wamp 环境 这个提示就是说你的版本低于10了。 先打印ph...
psycopg2.OperationalError: SSL SYSCALL error: EOF detect...