有没有方法能够理解复杂的SQL语句?在阅读结构化/面向对象的代码时,通常有多层抽象帮助你将其分解成可管理的部分。然而,在SQL中,似乎你必须同时跟踪查询的多个部分。
提出这个问题的动机是关于复杂连接的SQL查询的 这个问题。在盯着回答中的查询语句数分钟后,我最终决定使用特定记录逐步执行查询,以了解每个部分正在发生什么。那是我能想到的唯一理解查询的方法。
有没有更好的方法将SQL查询分解成可管理的部分?
当我看到一段复杂的SQL代码时,我会做以下事情:
首先,如果是更新或删除操作,并且没有被注释掉的话,我会添加代码将其转为选择操作。第一次尝试更新或删除操作之前,一定要先进行选择操作并查看结果。如果是更新操作,我会确保所选内容显示当前值以及我将设置的目标值,以确保我得到预期的结果。
理解连接非常关键,尤其是在理解复杂SQL的过程中。对于每个连接,我都会问自己它为什么存在?有四个基本原因:你需要一个列来选择、你需要一个字段用于where子句、你需要连接作为第三个表的桥梁,或者你需要连接到表以过滤记录(例如检索具有订单但不需要订单详情的客户的详细信息,这通常可以通过IF EXISTS where子句更好地完成)。如果是左连接或右连接(我倾向于重写所有内容使其成为左连接,这样生活会更简单),我会考虑是否可以使用内连接。为什么我需要左连接?如果我不知道答案,我会运行两种方式并查看数据之间的差异。如果有派生表,我会首先查看它们(只运行选择的部分以了解结果),以了解它们存在的原因。如果有子查询,我会尝试理解它们,并且如果它们很慢,我会尝试将其转换为派生表,因为这些通常更快。
接下来,我会查看where
子句。这是你在特定数据库中建立扎实基础的地方之一。例如,我知道在我的数据库中,有哪些场合可能只需要查看邮寄地址,有哪些场合可能需要查看其他地址。这可以帮助我知道where
子句中是否遗漏了某些内容。否则,我会考虑where
子句中的每一项,并弄清楚为什么它需要在那里,然后我会考虑是否有任何遗漏的内容应该在那里。在检查完毕后,我会考虑是否可以进行调整以使查询可搜索。
以下是一些有用的提示:
例如:
select ID, Description, Status from ABC where Status = 1 OR Status = 3
可以更好地改写为:
select
ID,
Description,
Status
from ABC
where
Status = 1 OR
Status = 3
如果使用更复杂的查询,你会看到更大的优势。
以下是遵循的步骤来解决查询问题。
执行每个步骤通常能让我更好地了解查询中发生了什么。
缩进和注释可以帮助很多。 我遇到的最有价值的东西是WITH语句。它在Oracle中,用于子查询重构。它允许您将一个大查询分解成一组看似较小的查询。每个查询都更易于管理。
这里是一个示例
WITH
ssnInfo AS
(
SELECT SSN,
UPPER(LAST_NAME) LAST_NAME,
UPPER(FIRST_NAME) FIRST_NAME,
TAXABLE_INCOME,
CHARITABLE_DONATIONS
FROM IRS_MASTER_FILE
WHERE STATE = 'MN' AND -- limit to in-state
TAXABLE_INCOME > 250000 AND -- is rich
CHARITABLE_DONATIONS > 5000 -- might donate too
),
doltishApplicants AS
(
SELECT SSN,
SAT_SCORE,
SUBMISSION_DATE
FROM COLLEGE_ADMISSIONS
WHERE SAT_SCORE < 100 -- About as smart as a Moose.
),
todaysAdmissions AS
(
SELECT doltishApplicants.SSN,
TRUNC(SUBMISSION_DATE) SUBMIT_DATE,
FIRST_NAME
FROM ssnInfo,
doltishApplicants
WHERE ssnInfo.SSN = doltishApplicants.SSN
)
SELECT 'Dear ' || FIRST_NAME ||
' your admission to WhatsaMattaU has been accepted.'
FROM todaysAdmissions
WHERE SUBMIT_DATE = TRUNC(SYSDATE) -- For stuff received today only
;
使用内联视图也可以实现相同的功能,但“with”还能在需要时创建临时表。在某些情况下,您可以将子查询复制并在大查询上下文之外执行它。
此形式还允许您将过滤子句与各个子查询一起放置,并将连接子句保存到最终选择中。
在我们的工作中,开发团队通常发现这种方法更易于维护,而且通常更快。
大多数情况下,这只需要经验和适当的缩进。
格式化有所帮助,但理解集合论以及关系理论更有帮助。
对查询执行方式的模糊了解也不会有害(表扫描、索引扫描、索引跳跃、哈希表合并等);查询计划器可以显示这些操作。
一些操作(如having、exists、with)起初可能会有些棘手。
首先要理解每个表发生了什么,以及表是如何连接的。
我发现回到逻辑查询处理阶段,并使用样本数据逐步分解查询通常是有帮助的。
(以下内容摘自Itzik Ben-Gan的《Microsoft SQL Server 2005内部:T-SQL查询》。)
(8) SELECT (9) DISTINCT (11) <TOP_specification> <select_list>
(1) FROM <left_table>
(3) <join_type> JOIN <right_table>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) WITH {CUBE | ROLLUP}
(7) HAVING <having_condition>
(10) ORDER BY <order_by_list>
另一个重要的是使用标准的连接语法:
SELECT A
FROM B
JOIN C ON B.ID = C.ID
WHERE C.X = 1
不是
SELECT A
FROM B
, C
WHERE B.ID = C.ID
AND C.X = 1
我想这完全取决于经验。我觉得那些问题中的查询并不是很复杂,可能是因为我经常运行的查询比那些更复杂。
合适的编码标准当然有助于理解查询,因为它可以将查询分成视觉上较小和格式更好的块。当涉及子查询时,最好首先了解它们的功能,并在查看完整的查询时使用该理解。