有没有人有方法来理解复杂的SQL语句?在阅读结构/ OO代码时,通常有一些抽象层可以帮助您将其分解为可管理的块。但是,通常在SQL中,您似乎必须同时跟踪查询的多个部分中发生的事情。
此问题的推动力是this question about a complex join中讨论的SQL查询。在盯着答案查询几分钟后,我终于决定使用特定记录逐步查询查看发生了什么。这是我能想到的唯一方法来逐一理解查询。
有没有更好的方法将SQL查询分解为可管理的部分?
答案 0 :(得分:37)
当我查看一段复杂的SQL代码时,这就是我的工作。
首先,如果是更新或删除,我添加代码(如果它不存在并注释掉)以使其成为选择。切勿首次尝试更新或删除,而不先在选择中查看结果。如果是更新,我确保选择显示当前值以及我将其设置为什么,以确保我得到所需的结果。
了解联接对于理解复杂的SQL至关重要。对于每次加入我都会问自己为什么会这样?有四个基本原因。你需要一个select列的列,你需要一个where子句的字段,你需要join作为第三个表的桥接,或者你需要加入到表中来过滤记录(比如检查有订单的客户的详细信息)但是不需要订单细节,这通常可以通过IF EXISTS where子句更好地完成。如果是左连接或右连接(我倾向于重写所以一切都是左连接,这使生活更简单。),我考虑内部连接是否有效。为什么我需要左连接?如果我不知道答案,我会双向运行,看看数据中的差异是什么。如果有派生表,我将首先看一下(运行select的那一部分以查看结果是什么)来理解它为什么存在。如果有子查询,我将尝试理解它们,如果它们很慢,将尝试转换为派生表,而不是因为它们通常要快得多。
接下来,我查看where
条款。在这个地方,您的特定数据库中的坚实基础会派上用场。例如,我知道在我的数据库中,我可能需要查看邮件地址以及我可能需要查看其他地址的场合。这有助于我知道where子句中是否缺少某些内容。否则,我会考虑where
子句中的每个项目并弄清楚为什么需要在那里,然后我会考虑是否有任何遗漏应该存在。仔细研究之后,我会考虑是否可以进行调整以使查询更具可靠性。
我还会考虑下一个选择列表的任何复杂位。那个案例陈述做了什么?为什么有子查询?这些功能有什么作用? (我总是查找我不熟悉的任何函数的函数代码。)为什么有明显的?是否可以通过使用派生表或聚合函数和group by语句来消除它?
最后,最重要的,我运行选择并根据我对业务的了解确定结果是否正确。 如果您不了解您的业务,您将不知道查询是否正确。语法正确并不意味着正确的结果。通常,您现有的用户界面中有一部分可用作指导结果是否正确的指南。如果我有一个显示客户订单的屏幕,并且我正在做包含客户订单的报告,我可能会检查一些个别客户,以确保它显示正确的结果。
如果当前查询过滤不正确,我将删除它的一些内容,以找出什么是摆脱我不想要的记录或添加我不想要的记录。通常你会发现连接是一对多的,你需要一对一(在这种情况下使用派生表!)或者你会发现你认为在where
子句中需要的一些信息是对于您需要的所有数据或缺少where
子句的某些部分都是如此。在您执行此操作时,选择中的where
子句中的所有字段(如果它们不在选择中)都有帮助。它甚至可以帮助显示所有连接表中的所有字段并真正查看数据。当我这样做时,我经常在where子句中添加一小部分来获取我不应该存在的一些记录而不是所有记录。
一个会破坏大量查询的偷偷摸摸的事情是where
子句引用左连接右侧表中的字段。这将它变成了一个内部联接。如果您确实需要左连接,则应将这些条件添加到连接本身。
答案 1 :(得分:17)
这些可能是一些有用的提示..
例如:
select ID, Description, Status from ABC where Status = 1 OR Status = 3
可以更好地写成:
select
ID,
Description,
Status
from ABC
where
Status = 1 OR
Status = 3
通过更复杂的查询,您会看到更大的好处。
答案 2 :(得分:12)
以下是解开查询的过程。
执行这些操作通常可以让我更好地掌握查询中发生的事情。
答案 3 :(得分:9)
主要是经验和适当的缩进。
答案 4 :(得分:8)
缩进和评论有很多帮助。 我遇到的最有价值的事情是WITH语句。它在Oracle中,处理子查询重构。它允许您将大型查询分解为一组看似较小的查询。每一个都更容易管理。
这是一个例子
WITH
ssnInfo AS
(
SELECT SSN,
UPPER(LAST_NAME),
UPPER(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,
LAST_NAME, FIRST_NAME,
TAXABLE_INCOME
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
;
使用内联视图可以完成同样的事情,但是还可以在需要时创建临时表。在某些情况下,您可以复制出子查询并在大型查询的上下文之外执行它。
此表单还允许您将过滤器子句与单个子查询放在一起,并保存最终选择的连接子句。
在工作中,我们的开发小组通常会发现它们更容易维护,而且通常更快。
答案 5 :(得分:5)
格式化有所帮助,但理解集合论和扩展,关系理论,可以帮助更多。
对查询执行方式的模糊理解也不会造成影响(表扫描,索引扫描,索引跳转,哈希表合并等);查询计划程序可以向您显示这些操作
一些操作(有,存在,有)最初可能很麻烦
首先了解每个表的内容以及表的连接方式
答案 6 :(得分:2)
我猜这一切都取决于经验。我没有发现那些问题中的查询非常复杂,因为我运行的大多数查询都比那些查询更复杂。
正确的编码标准肯定有助于理解查询,因为它允许将其分解为视觉上更小和更好的格式化块。涉及子查询时,最好先了解这些内容的作用,并在查看完整查询时使用这种理解。
答案 7 :(得分:2)
另一个重要的是使用标准连接语法:
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
答案 8 :(得分:2)
与任何事情一样,最好的方法是自己编写大量复杂的SQL语句。最终,事物结构的一般方式变得明显。当然,如果你正在寻找一些可能不是那么快的东西。
白色空间非常重要。当存在适当的空白区域时,看起来非常复杂的查询看起来几乎过于简单。
关于联接...嗯,我很抱歉,但我在这里不能提供很多帮助,因为我的答案是理解特定联接的最佳方式是了解联接的工作方式。每种类型的连接都有一个非常特定的目的,如果你知道它们是如何工作的,那么将x连接到y,x到y到z,或者x和y连接到a和b,应该没有什么区别。
然而,更有帮助的是,你知道你需要先看看最里面的部分。与您可能习惯于大规模查看事物的代码相反,然后深入研究粒度,通过查询,如果您从粒度开始并向外工作,则更有帮助且更容易理解。
从任何子查询开始,找出他们在单个部分中所做的事情,将其视为单个查询(如果可能),然后逐步逐步移出,直到您处于顶部。再次,在连接上...真的,只需找到一个解释连接的网页,并进行一些测试,直到你完全理解它们为止。没有办法让这更容易,因为一旦你了解它们,你几乎可以想出任何你想要的连接。
答案 9 :(得分:1)
你正在做我做的事。我的第一个使查询易于理解的工具是良好的可视化组织,您引用的问题中的人主要在做,并使用LIMIT子句以可管理的块进行测试。如果涉及非相关子查询,它们当然可以单独运行。但是,如果有一个神奇的子弹,我不知道它。
答案 10 :(得分:1)
如果这些让你暂停,我建议在纸上写出表格,以便更好地了解将各种事物结合起来的意义。
假设您有一个Books表和一个Table表。价格表可能每本书有多个条目(因为价格可能会改变)。
如果您想获得当前图书和价格的清单,您必须将两张桌子加在一起。
我通过在每本书和相应的“当前”价格之间画箭头来解决这个问题。然后我将它写入逻辑,它将成为连接条件或子查询的一部分。
一旦掌握了它,复杂的查询就会更容易解析。
答案 11 :(得分:1)
我发现回到逻辑查询处理阶段并逐步取消查询以获取样本数据通常很有帮助。
(以下内容来自于Microsoft SQL Server 2005内部:Itzik Ben-Gan的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>
答案 12 :(得分:0)
使用CTE或派生表(至少在MS SQL中)可以用于格式化SQL语句,而无需使用临时表将它们拆分为单独的查询来“加入”它们。
我同意其他人的意见,提到的查询非常简单。
我看着c#并想知道为什么你有这么多行来处理几千行......
答案 13 :(得分:0)
如果你正在使用PostgreSQL,那么查看封装就很棒了。
答案 14 :(得分:-1)
我把它分解成更小的查询(这就是为什么我比JOIN更喜欢子查询)
有时我甚至将子查询的结果保存为表,并在主查询中使用它。这有点像通过将位保存到局部变量中然后对表达式的下一部分中的局部变量进行操作来简化代码表达式。
我总是喜欢使用表别名(例如CLIENT_HISTORY_ITEM T1)和围绕条件表达式的括号。对于查询的每个部分,我经常将表别名数改为10左右,这样我就能看到来自哪里的内容:
选择T1.ID FROM TABLE1 T1 在哪里T1.DATE = (SELECT MAX(T11.DATE) 从TABLE1 T11 在哪里(T1.AREA = T11.AREA))
干杯
答案 15 :(得分:-2)
查询优化器可以处理很多,包括将子查询实现为连接。现在,他们甚至可以处理不相关的子查询。
在大多数情况下,清晰度比性能更重要,子查询更容易调试。
BTW:你为什么要使用令人困惑的表别名?