SQL查询语句应该避免的五个错误

SQL查询语句应该避免的五个错误

SQL和机器学习有一些相似之处。它们都不需要大量的编程,所以上手比较容易,而且代码也很少会崩溃。

与SQL和机器学习相关推文

我认为,SQL很难崩溃会让数据分析变得更加困难。比如从数据库中提取数据集,结果却发现了数据错误或缺失数据,这种情况出现的次数有多少呢?答案是:相当多!

但如果SQL在错误时直接崩溃,我反而很容易知道自己犯错了。因为SQL查询一定会返回结果,而这些结果就需要数据科学家花费大量时间进行数据的验证。

所以在编写SQL查询时,以下五种错误是你应该注意的。

01 不清楚SQL执行的顺序

SQL 查询执行顺序

SQL的使用门槛很低。在你编写查询时,如果能用JOIN进行一些分组,很多人都会觉得你已经是专家了。

但是,这些所谓的专家知道SQL查询是按什么顺序执行的吗?

SELECT是在编写时在编辑器中执行的,但数据库并不是这样,所以SQL查询其实并不是从SELECT开始的。

数据库是从FROM和JOIN开始执行查询的。这也就解释了为什么我们可以用WHERE来使用已经JOIN过的表格。

那为什么我们不能过滤掉WHERE中的GROUP BY呢?这是因为,GROUP BY是在WHERE之后执行的,所以我们还需要下一步:HAVING。

最后,运行SELECT。它选择了要包括的列,定义了要计算的聚合函数。另外,Window Functions也在这里执行。

这就是为什么当我们尝试在WHERE中使用Window Function的输出进行过滤时,会出现错误。

请注意,数据库是利用查询优化器(query optimizer)来优化查询执行的。优化器可能会改变某些操作的顺序,让查询运行更快。但这是一些幕后的高级概念。

02 Window Functions的实际功能

使用SUM窗口函数进行转换的示例

第一次遇到Window Functions时,我觉得很神奇。为什么把Window Functions作为GROUP BY用的时候,可以聚合数据呢?

其实,在设计查询时,Window Function(WF)简化了很多操作:

  • WF能访问当前记录前后的记录,具体参见Lead和Lag函数。
  • WF可以用GROUP BY对已经聚合的数据执行额外的聚合。请看上图中的例子,我用了WF计算销售额。
  • ROW_NUMBER WF可以列举行(row)。还可以用它来删除重复的记录,或者随机抽取样本。

顾名思义,WF还可以在窗口里计算统计数据:

上述的WF,可以计算第一条记录到当前记录的累计总和。那在这一过程中,我在哪里出错了?

我没有花时间去了解Window Functions的基础知识和功能教程。所以,我的查询过程变得过于复杂,还出现了bug。

运行以上查询:

在SQL Fiddle中运行的示例

如果你想在本地数据库中跟着练习,可以尝试这个代码(与PostgreSQL 9.3一起使用)

03 用CASE WHEN计算平均值

CASE WHEN就像是编程语言中的IF语句。当我们需要计算某个数据子集的统计数据时,它会很有用。

上图中,我计算了美国销售产品的平均价格。在CASE WHEN中,我使用ELSE的时候没有特别注意。

在第一个例子中,我用0表示了所有的非美国产品价格,而这导致了总体平均价格降低。如果有很多非美国的产品,那么平均值可能接近于0。

在第二个例子中,我只计算了在美国销售的产品的平均价格,这也是我们想要的结果。

请注意,使用CASE WHEN时,不是必须包括ELSE的,它的默认值是NULL。

重点是,使用CASE WHEN时,应该小心使用“else 0”。虽然它对SUM没有任何影响,但对AVG影响很大。

04 用Join连接缺少值的列

在两个字符串字段上的简单inner join

SQL中有4种不同的连接:内连接(inner join)、外连接(outer join)、左连接(left join)和右连接(right join)。当我们直接使用JOIN时,默认是INNER连接。

之前,我花了时间去阅读一些JOIN相关的教程。但我还是犯了一个低级错误。

我写了一个与上面类似的Join查询。但是当我执行数据验证时,发现很多记录不见了。这只是一个简单的JOIN函数,为什么会出现这种情况呢?

结果表明,表1和表2中都有许多带有NULL值的string_field列。我以为JOIN会保留NULL值的记录,因为NULL值与NULL应该是一样的,不是吗?

所以我做了以下尝试:

返回结果为NULL。

如果想要返回所有条目,就需要处理COALESCE中所有的string_field,它会把所有NULL转换为空字符串。

但要注意的是,这个Join会把table1中的每个空字符串与table2中的每个空字符串连接起来。

删除这些重复项的一种方法,是使用ROW_NUMBER()的窗口函数(window function)

  • 我假设你每一行的“ some_id” 都有唯一的标识符和一个时间戳。
  • 只需进行整体查询,并留下每个标识符的第一行,就可以删除重复项了。

05 没有在进行复杂查询时使用临时的表

在临时表中包装查询

如果我们能够调试查询,那SQL会很好用。那如果我告诉你真的可以调试它们呢?

你可以把一个复杂的查询分解成多个临时的表。然后可以对这些表运行“完整性检查(sanity check)”,确保它们包含了正确的条目。特别是,在设计新的重要查询或报告时,我强烈推荐这种方法。

临时表的唯一缺点,就是查询优化器不能优化临时表里的查询。

需要提升性能时,我会用 WITH 语句重写一条查询。

感谢你的阅读!希望以上内容可以帮助你解决一些SQL中的常见错误。你还可以订阅我们的YouTube频道,观看大量数据科学相关公开课:https://www.youtube.com/channel/UCa8NLpvi70mHVsW4J_x9OeQ;在LinkedIn上关注我们,扩展你的人际网络!https://www.linkedin.com/company/dataapplab/

原文作者:Roman Orac
翻译作者:Lea
美工编辑:过儿
校对审稿:Jiawei Tong
原文链接:https://towardsdatascience.com/dont-repeat-these-5-mistakes-with-sql-9f61d6f5324f