技术 2023.10.09 81 阅读

SQL查询并不是从SELECT开始的

你可能和我一样,写了很多SQL,但从未深究过它的执行顺序。实际上SQL的逻辑执行顺序,和我们书写的顺序完全不同。

· · ·

显然很多 SQL 查询确实以 SELECT 开始(本文仅涉及 SELECT 查询,而不涉及 INSERT 或其他内容)。

  昨天我在做一个关于窗口函数的解释时,发现自己在谷歌上搜索“你能根据窗口函数的结果进行过滤”。比如说——你能在WHERE或HABEING中过滤窗口函数的结果吗?

最终我得出结论:“窗口函数必须在 WHERE 和 GROUP BY 之后运行,所以你不能这么做”。但这也让我产生了一个更大的问题——SQL查询到底是按什么顺序运行的?

 这是我直觉上知道的事情(“我至少写过一万个SQL查询,其中一些非常复杂!我必须知道这些!“)但我很难真正表达顺序。


SQL 查询的顺序如下

我查到了顺序!(SELECT不是第一步,而是第五步)


在非图像格式中,顺序为

FROM/JOIN和所有ON条件
WHERE
GROUP BY
HAVING
SELECT(包含窗口函数)
ORDER BY
LIMIT


借助图表可以得出结论

 这张图介绍了SQL查询的语义——它让你推理出给定查询会返回什么,并回答诸如此类的问题:

我能先GROUP BY再用WHERE吗?(不!WHERE发生在GROUP BY之前!)

我可以根据窗口函数的结果进行筛选吗?(不!窗口函数发生在SELECT中,SELECT发生在WHERE和GROUP BY之后)

我可以根据我在GROUP BY里做的事情来“ORDER BY”吗?(是的!ORDER BY基本上是最后一个,你可以根据任何东西来ORDER BY!)

LIMIT 什么时候发生?(在最后!)

实际上数据库引擎并不是按这个顺序运行查询的,因为他们做了大量优化来加快查询速度---我们稍后会详细说明。

所以:

 这张图最适合用来判断 SQL 写法是否合法,以及推演一条查询最终会返回什么结果。

 你不应该用这个图来推理查询性能或涉及索引的内容,那是更复杂且变量更多的问题


混淆因素:列别名

有人在推特上指出,许多SQL实现允许你使用以下语法:

SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)
FROM table
GROUP BY full_name


这个SQL查询让GROUP BY看上去像是在SELECT之后发生的,尽管GROUP BY是第一个,因为GROUP BY引用了SELECT里面的别名。但实际上并不一定--数据库引擎会将查询重写为

SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)
FROM table
GROUP BY CONCAT(first_name, ' ', last_name)


并先运行GROUP BY。

你的数据库引擎在执行查询前会先做一系列检查,确保你在 SELECT和 GROUP BY里写的字段名是否能正确匹配。因此,它在着手制定执行计划之前,本来就需要把整条查询作为一个整体来分析。因此在制定执行计划之前,本来就需要将整条查询作为一个整体来分析。


查询实际上并非安此顺序运行的!(优化)

数据库引擎实际并非是按链表、筛选然后分组来进行查询,在保证查询结果不变的前提下,他们会进行大量优化 ,重新排序来让查询运行的更快。

用一个直观的例子来解释为什么需要通过调整执行顺序来进行提速。

SELECT * FROM
owners LEFT JOIN cats ON owners.id = cats.owner
WHERE cats.name = 'mr darcy'

如果你只需要查找3只名叫“myr darcy”的猫,最笨的方法是做整个左连接并匹配两张表中所有行。但是先筛选就会快很多。而且在这种情况下,先过滤并不会改变查询结果!

数据库引擎实际上还有很多优化,可能会让查询顺序不同,但我没有这个功夫,说实话我也不是专家。


LINQ从FROM开始查询

LINQ(C# 和 VB.NET 中的查询语法)使用顺序FROM...WHERE...SELECT。这里有一个示例:

var teenAgerStudent = from s in studentList
                      where s.Age > 12 && s.Age < 20
                      select s;

Pandas(我最喜欢的数据处理工具)基本上也是这样工作的。虽然你不必完全按照这个顺序--我经常会这样写Pandas代码:

df = thing1.join(thing2)      # like a JOIN
df = df[df.created_at > 1000] # like a WHERE
df = df.groupby('something', num_yes = ('yes', 'sum')) # like a GROUP BY
df = df[df.num_yes > 2]       # like a HAVING, filtering on the result of a GROUP BY
df = df[['num_yes', 'something1', 'something']] # pick the columns I want to display, like a SELECT
df.sort_values('sometthing', ascending=True)[:30] # ORDER BY and LIMIT
df[:30]


Pandas并没有强制要求代码顺序,而是按JOIN/WHERE/GROUP BY/HAVING这个顺序写更符合“直觉”。(尽管为了性能,我经常把过滤条件前置。而且我相信大多数数据库引擎在物理执行时也确实会先做WHERE条件过滤)

R 里还允许你用不同的语法来查询 Postgres、MySQL 和 SQLite 等 SQL 数据库,顺序也更合逻辑。

我真的很惊讶自己竟然不知道这些

我写博客是因为当我知道这个顺序时,我非常惊讶以前从没见过这样写下来---它基本很直观的解释了为什么我认为有些查询允许,而有些不允许。所以我想把它写下来,希望能帮助其他人理解如何写SQL查询.



原文:SQL queries don’t start with SELECT


译后记

其实搞懂顺序后,很多面试题答起来都得心应手。比如我在面试一些年轻开发的时候经常会问到的mysql实战问题

1.a表为记录表,我想要查找出超过n条记录的用户有哪些。核心点就在于GROUP BY后面要用HAVING过滤聚合结果

2.比如我想要取每个用户最新一条数据或取前n条数据等等