为什么SQL不可重构?[关闭]


39

每个人都知道新开发人员会编写长函数。随着您的进步,您会更好地将代码分解成较小的部分,而经验告诉您这样做的价值。

输入SQL。是的,SQL的代码思考方式与过程的代码思考方式不同,但是该原理似乎同样适用。

假设我有一个采用以下形式的查询:

select * from subQuery1 inner join subQuerry2 left join subquerry3 left join join subQuery4 

使用一些ID或日期等

这些子查询本身很复杂,可能包含自己的子查询。在任何其他编程上下文中,我都认为复杂子查询1-4的逻辑与将它们全部连接在一起的父查询一致。看起来很简单,应该将那些子查询定义为视图,就像如果我在编写过程代码时将它们作为函数一样。

那么,为什么不这样做呢?人们为什么如此频繁地编写这些冗长的整体SQL查询?SQL为什么不鼓励广泛使用视图,就像过程编程鼓励广泛使用函数一样。(在许多企业环境中,创建视图甚至都不是一件容易的事。需要请求和批准。想象一下,其他类型的程序员每次创建函数时是否都必须提交请求!)

我想到了三个可能的答案:

  1. 这已经很普遍了,我正在与经验不足的人一起工作

  2. 经验丰富的程序员不会编写复杂的SQL,因为他们喜欢用过程代码解决硬数据处理问题

  3. 还有别的


12
有些组织仅允许您通过视图查询数据库并通过存储过程对其进行修改。
Pieter B

3
当我最终接受到SQL永远不会像普通的过程代码一样干燥时,SQL对我来说变得更加有趣。

1
4. SQL确实很老,几十年来一直没有实质性的更新。对于超级复杂的东西,很多团队都选择存储过程。您可以为此添加不同的子句。有时,您只需要运行作业就可以将数据暂存到临时表中,然后再加入该表中。看一下声明性和程序性语言有何不同。
Berin Loritsch

8
还有一个原因是,存在一个可怕的性能问题,称为“三角连接”,当您使用视图时,可能会发生这种情况(当然很偶然)。如果您的查询联接视图A和视图B,但观在执行重新使用View B,你开始看到这个问题。因此,人们通常从编写单个整体查询开始,以便能够看到在重构视图方面最有效的方法,然后按时完成任务,然后整体就可以生产了。大约占所有软件开发人员的98%,真的:) :)
Stephen Byrne

3
“想象一下,其他类型的程序员每次创建函数时是否都必须提交请求”。您不进行代码审查吗?
svidgen

Answers:


25

我认为主要问题是,并非所有数据库都支持通用表表达式。

我的雇主在很多方面都使用DB / 2。它的最新版本支持CTE,因此我可以执行以下操作:

with custs as (
    select acct# as accountNumber, cfname as firstName, clname as lastName,
    from wrdCsts
    where -- various criteria
)
, accounts as (
    select acct# as accountNumber, crBal as currentBalance
    from crzyAcctTbl
)
select firstName, lastName, currentBalance
from custs
inner join accounts on custs.accountNumber = accounts.accountNumber

结果是我们可以使用简略的表/字段名,而我实际上是在创建临时视图,并使用更清晰的名称,然后可以使用它们。当然,查询时间会更长。但是结果是,我可以编写一些很明显分开的内容(使用CTE,就像使用函数获取DRY的方式一样),并且最终得到了清晰易懂的代码。而且因为我能够分解自己的子查询,并让一个子查询引用另一个子查询,所以它并不都是“内联的”。有时,我编写了一个CTE,然后又有四个其他CTE都引用了它,然后让主查询合并了最后四个的结果。

这可以通过以下方式完成:

  • DB / 2
  • PostGreSQL
  • 甲骨文
  • MS SQL服务器
  • MySQL(最新版本;还是新版本)
  • 可能是其他人

但是,这将使代码更清晰,更清晰,更干燥。

我已经开发了CTE的“标准库”,可以将其插入各种查询中,从而使我可以快速开始新查询。我组织中的其他开发人员也开始接受其中的一些。

随着时间的流逝,将其中一些转换为视图可能是有意义的,因此无需复制/粘贴即可使用此“标准库”。但是我的CTE最终因各种需求而进行了微调,以至于我无法在没有Mod的情况下如此广泛地使用单个CTE,因此可能值得创建视图。

您似乎不高兴的部分是“我为什么不了解CTE?” 或“为什么我的数据库不支持CTE?”

至于更新...是的,您可以使用CTE,但以我的经验,您必须在set子句和where子句中使用它们。如果您可以在整个update语句之前定义一个或多个,然后在set / where子句中仅包含“ main query”部分,那将是很好的选择,但是这样做不起作用。而且,无法避免要更新的表上的表/字段名称晦涩难懂。

您可以使用CTE进行删除。可能需要多个CTE才能确定要从该表中删除的记录的PK / FK值。同样,您无法避免要修改的表上的表/字段名晦涩难懂。

由于可以对插入进行选择,因此可以使用CTE插入。与往常一样,您可能正在处理要修改的表上晦涩的表/字段名称。

SQL不允许您使用getters / setters创建与包装表的域对象等效的方法。为此,您将需要使用某种ORM,以及更具过程性的/ OO编程语言。我已经用Java / Hibernate编写了这种性质的东西。


4
我们让Big CTE先生来编写最糟糕的SQL。问题是热膨胀系数很差抽象的选择和优化不能撤消每一个愚蠢的算法,你把英寸
约书亚

3
另外,ORM还能在性能方面做一些令人发指的事情……尤其是当您仅使用getter和setter来获取大量数据时。Hibernate因使用数百个单个查询而不是一个大型联合查询而臭名昭著,当每个查询都有开销时,这是一个问题。
user3067860

2
@Joshua您可以使用任何语言编写错误的代码。包括SQL。但是正确地重构为CTE可以创建自底向上的设计,使人更容易解析。我倾向于将其视为理想的特性,无论我使用什么语言:-)
Meower68

2
其他答案很好,但这就是我个人想要的。“我为什么不了解CTE”是我的大部分问题。
ebrts

2
@ Meower68是否存在广泛使用CTE阻止人们学习正确加入和学习良好数据库设计的风险?我支持CTE的价值,但它也使您在不应该使用子查询的情况下工作变得非常容易。
Pieter B

36

锁定数据库视图的创建通常是由组织对数据库中的性能问题抱有幻想。这是组织文化问题,而不是SQL的技术问题。

除此之外,大型单片SQL查询被多次编写,因为用例是如此具体,以至于很少的SQL代码可以真正在其他查询中重用。如果需要一个复杂的查询,通常是一个非常不同的用例。从另一个查询复制SQL通常是一个起点,但是由于其他子查询和新查询中的JOIN,您最终修改复制的SQL的程度足以破坏使用另一种语言的“函数”所能进行的任何抽象。用于。这使我想到了难以重构SQL的最重要原因。

SQL仅处理具体的数据结构,而不处理抽象的行为(或任何意义上的抽象)。由于SQL是围绕具体思想编写的,因此没有什么可以抽象成可重用模块的了。数据库视图可以帮助解决此问题,但不能达到另一种语言的“功能”级别。数据库视图与其说是查询,不如说是抽象。好吧,实际上,数据库视图就是一个查询。它本质上像表一样使用,但是像子查询一样执行,因此,您要处理的是具体的东西,而不是抽象的东西。

有了抽象,代码就变得易于重构,因为抽象将实现细节从该抽象的使用者中隐藏起来。尽管对SQL的过程扩展(例如,用于Oracle的PL / SQL或用于SQL Server的Transact-SQL)对SQL的过程扩展开始稍微模糊了界限,但Straight SQL没有提供这种分离。


“ SQL只处理具体的数据结构,而不处理抽象的行为(或任何意义上的抽象)。” 这是一个奇怪的陈述,因为从我的角度来看,SQL完全处理抽象行为,而不是任何意义上的具体编程!只需考虑一下抽象为简单单词“ JOIN”的所有复杂程度:您说您想要一个从两个不同数据集中得出的合并结果,然后交给DBMS来确定所涉及的具体技术,索引,处理表和子查询之间的差异等...
Mason Wheeler

5
@MasonWheeler:我想我更多地是从它处理的数据的角度来考虑SQL,而不是语言功能的实现。数据库中的表似乎不是抽象的。它们是具体的,因为在名为“ phone_numbers”的表中包含电话号码。电话号码不是一个抽象的概念。
Greg Burghardt

12

从问题/观点来看,我认为您可能会缺少的是SQL对集合执行操作(使用集合操作等)。

当您在该级别上操作时,自然会放弃对引擎的某些控制。您仍然可以使用游标强制使用某些程序样式代码,但是由于经验显示99/100次,您不应该这样做。

重构SQL是可行的,但它没有使用与我们在应用程序级代码中惯用的相同的代码重构原理。相反,您可以优化使用SQL引擎本身的方式。

这可以通过多种方式来完成。如果您使用的是Microsoft SQL Server,则可以使用SSMS为您提供大致的执行计划,并可以使用它来查看可以执行哪些步骤来调整代码。

如@ greg-burghardt所述,在将代码拆分为较小的模块的情况下,SQL通常是专门构建的代码段,因此也是。它完成了您需要它做的一件事,而没有其他任何事情。它坚持SOLID中的S,只有一个原因会被更改/影响,那就是您需要该查询执行其他操作时。首字母缩略词(OLID)的其余部分在此处不适用(AFAIK在SQL中没有依赖项注入,接口或依赖项),取决于您所使用的SQL的风格,您也许可以通过包装某些查询来扩展它们在存储过程/表函数中或将它们用作子查询,因此,在某种程度上,我会说开闭原则仍然适用。但是我离题了。

我认为您需要在查看SQL代码的方式上转变自己的范例。由于它的设置性质,它不能提供应用程序级语言可以提供的许多功能(泛型等)。SQL从未被设计成类似这样的东西,它是一种查询数据集的语言,并且每个数据集都有其自己的独特方式。

话虽这么说,但是在组织内部,将可读性放在首位的方式是,可以使代码看起来更好。将经常使用的SQL块(您使用的通用数据集)的位存储到存储过程/表值函数中,然后查询并将它们存储在临时表/表变量中,然后使用它们将这些块结合在一起成为一个大规模事务您否则会写的是一个选择。恕我直言,用SQL做类似的事情是不值得的。

作为一种语言,它的设计目的是使任何人,甚至非程序员都易于阅读和理解。这样,除非您做的非常聪明,否则无需将SQL代码重构为较小的字节大小的片段。我个人在使用数据仓库ETL / Reporting解决方案时编写了大量的SQL查询,就发生的事情而言,一切仍然非常清楚。任何看起来对其他任何人都有些奇怪的东西都会附带简短的注释,以提供简短的解释。

我希望这有帮助。


6

在您的示例中,我将重点介绍“子查询”。

为什么经常使用它们?因为他们使用自然的人思维方式:我拥有这组数据,并且想要对其一部分进行操作并将其与其他数据的一部分合并。在我看到子查询的10次中,有9次使用错误。我关于子查询的笑话是:害怕加入的人使用子查询。

如果看到这样的子查询,通常也表明数据库设计不是最佳的。

数据库越规范化,您获得的连接越多,数据库看起来像一个很大的excel工作表,您得到的子选择就越多。

SQL重构通常具有不同的目标:获得更高的性能,更好的查询时间,“避免表扫描”。这些甚至可能使代码的可读性降低,但非常有价值。

那么,为什么您会看到这么多庞大的整体式非重构查询?

  • SQL在许多方面都不是编程语言。
  • 错误的数据库设计。
  • 人们不太会SQL。
  • 无法控制数据库(例如,不允许使用视图)
  • 重构的不同目标。

(对我来说,我对SQL的了解越丰富,查询的规模就越小,SQL可以让各种技能的人轻松完成工作。)


6
“子查询”也同样可能是一些聚集正确归分贝的,因为它们是临时非规范化的分贝正常化
Caleth

@Caleth的确如此。
Pieter B

5
即使在规范化的数据库中,仍然经常需要与子查询联接,而不是直接与表联接。例如,如果您需要加入分组数据。
Barmar

1
@Barmar肯定是,因此我的10条评论中有9条。子查询占有一席之地,但我发现经验不足的人过度使用了子查询。
Pieter B

我喜欢您的“子查询数”指标来表示数据库规范化(或缺少数据库规范化)。
杰森(Jason)

2

职责分工

按照SQL的精神,数据库是包含公司数据的共享资产,对其进行保护至关重要。进入DBA作为庙宇的监护人。

在数据库中创建新视图被认为具有持久的目的并由用户社区共享。在DBA视图中,只有在数据结构合理的情况下,这才是可接受的。然后,视图的每次更改都会为其所有当前用户(即使不是使用该应用程序但已发现该视图的用户)带来风险。最后,创建新对象需要管理授权,并且在视图的情况下,需要与基础表的授权保持一致。

所有这些解释了为什么DBA不喜欢添加仅用于某些单独应用程序代码的视图。

SQL设计

如果分解一个不错的复杂查询,则可能会发现子查询通常需要依赖于另一个子查询的参数。

因此,转换子查询不一定像说明的那么简单。您必须隔离变量参数,并设计视图,以便可以将参数作为选择标准添加到视图上。

不幸的是,这样做有时会比定制查询强加更多的数据,效率更低。

专有扩展

您可能希望通过将一些职责转移到SQL的过程扩展(例如PL / SQL或T-SQL)来进行一些重构。但是,这些依赖于供应商并产生技术依赖。另外,这些扩展在数据库服务器上执行,从而在资源上创建了更多的处理负载,这比应用程序服务器更难扩展。

但是到底是什么问题呢?

最后,职责分离和SQL设计及其强度和局限性是一个真正的问题吗?最后,事实证明,这些数据库能够成功,可靠地处理包括任务关键环境在内的非常关键的数据。

因此,为了实现成功的重构:

  • 考虑更好的沟通。尝试了解您的DBA的约束。如果您向DBA证明新视图已被数据结构证明是合理的,这不是一种即弃的解决方法,并且对安全性没有影响,那么他/她当然会同意创建它。因为,那将是共同的利益。

  • 首先打扫一下自己的房子:没有什么可以迫使您在很多地方生成大量SQL的。重构应用程序代码,隔离SQL访问,并创建类或函数以提供可重用的子查询(如果经常使用这些子查询)。

  • 提高团队意识:确保您的应用程序未执行DBMS引擎可以更有效地执行的任务。正如您正确指出的那样,程序方法和面向数据的方法不是由团队的不同成员同等地掌握的。这取决于他们的背景。但是,为了优化整个系统,您的团队需要了解整个系统。因此,要提高意识,以确保经验不足的玩家不会重新发明方向盘,而不会与经验丰富的成员分享其DB思想。


+1这里的一些要点。考虑到某些SQL有多糟糕,DBA允许视图的沉默通常是完全可以理解的。另外,如果SQL占用大量资源和/或将要频繁运行,则SQL肯定可以从同行评审中受益。
罗比·迪

1

关于第1点和第3点:视图并不是唯一的方法。根据RDBMS,还有临时表,市场,表变量,聚合列,CTE,函数,存储过程以及可能的其他构造。

DBA(和我既是DBA还是开发人员的人)通常倾向于以一种非常二进制的方式查看世界,因此由于感知的性能损失,经常会遇到诸如视图和功能之类的问题。

后来,人们意识到,尽管从NF角度来看,非规范化表虽然性能欠佳,但对复杂连接的需求却有所减少。

在第2点中提到的使用LINQ之类的技术在客户端进行查询的趋势也存在。

尽管我同意SQL在模块化方面可能具有挑战性,但尽管客户端代码与SQL之间始终存在二分法,但尽管4GL在某些方面使界限模糊,但取得了长足的进步。

我想这真的取决于您的DBA /架构师/技术负责人愿意为此放弃多少。如果他们拒绝使用除带有大量连接的原始SQL之外的任何内容,那么可能会导致大量查询。如果遇到这种情况,请不要将头撞在砖墙上,否则请升级。通常会有更好的做事方法,但会有所妥协-尤其是如果您可以证明其好处的话。


1
我从未听说过“集市”构造。那是什么?
主教

1
市场只是仓库(主数据库)的子集。如果有特定的复杂查询需要运行,则可以专门创建一个特殊的数据库来服务那些请求。一个非常常见的示例是报告市场。
罗比迪

1
困惑为什么这被否决了。它不会直接回答问题,但是会给出一个明确的隐式答案:“选项3:有很多处理此问题的方法,已被广泛使用”。
Dewi Morgan

关于数据集市的TIL。+1!
主教
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.