理解复杂SQL语句的最佳方法是什么?

时间:2008-12-18 20:05:32

标签: sql database

有没有人有方法来理解复杂的SQL语句?在阅读结构/ OO代码时,通常有一些抽象层可以帮助您将其分解为可管理的块。但是,通常在SQL中,您似乎必须同时跟踪查询的多个部分中发生的事情。

此问题的推动力是this question about a complex join中讨论的SQL查询。在盯着答案查询几分钟后,我终于决定使用特定记录逐步查询查看发生了什么。这是我能想到的唯一方法来逐一理解查询。

有没有更好的方法将SQL查询分解为可管理的部分?

16 个答案:

答案 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)

以下是解开查询的过程。

  1. 首先我格式化SQL。
  2. 然后我注释掉SQL的所有部分,而不是最主要或最重要的表的基本部分来回答问题。
  3. 然后我将开始取消注释连接,选择列,分组,顺序字段和&过滤器以查询查询的不同部分以查看发生的情况。或突出显示 - 执行在某些工具中有效。
  4. 子查询通常可以独立执行。
  5. 执行这些操作通常可以让我更好地掌握查询中发生的事情。

答案 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>
  1. FROM :在前两个表格之间执行笛卡尔积(交叉连接) FROM子句,因此生成虚拟表VT1。
  2. 开启:ON过滤器应用于VT1。只有TRUE的行 插入到VT2。
  3. OUTER(加入):如果指定了OUTER JOIN(而不是CROSS JOIN或者 INNER JOIN),保留的表中没有找到匹配项的行 从VT2作为外行添加到行中,生成VT3。如果超过两个表 出现在FROM子句中,步骤1到3在结果之间重复应用 最后一个连接和FROM子句中的下一个表,直到处理完所有表。
  4. WHERE :WHERE过滤器应用于VT3。只有行的哪个 是否为TRUE插入VT4。
  5. GROUP BY :VT4中的行根据指定的列列表按组排列 在GROUP BY子句中。 VT5已生成。
  6. CUBE | ROLLUP :超级组(组的组)被添加到VT5的行中, 生成VT6。
  7. HAVING :HAVING过滤器应用于VT6。只有哪些组  是否为TRUE插入VT7。
  8. SELECT :处理SELECT列表,生成VT8。
  9. DISTINCT :从VT8中删除重复的行。 VT9已生成。
  10. ORDER BY :VT9中的行根据中指定的列列表进行排序 ORDER BY子句。生成游标(VC10)。
  11. TOP :从开头选择指定的行数或百分比 VC10。生成表VT11并将其返回给调用者。

答案 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:你为什么要使用令人困惑的表别名?