用于简化SQL语句的一般规则

我正在寻找一些“推理规则”(类似于设置操作规则或逻辑规则),我可以用它来减less复杂性或大小的SQL查询。 有没有这样的东西? 任何文件,任何工具? 您自己find的任何等价物? 它在某种程度上类似于查询优化,但不是在性能方面。

说明它的不同:使用JOINs,SUBSELECTs,UNIONs进行(复杂)查询有可能(或不是)通过使用一些转换规则将其简化为更简单的等效SQL语句,该语句产生相同的结果?

所以,我正在寻找SQL语句的等效转换,就像大多数SUBSELECT可以重写为JOIN一样。

说明它的不同:使用JOINs,SUBSELECTs,UNIONs进行(复杂)查询有可能(或不是)通过使用一些转换规则将其简化为更简单的等效SQL语句,该语句产生相同的结果?

这正是优化者为了生活所做的(不是我说他们总是这么做)。

由于SQL是基于集合的语言,因此通常有多种方法将一个查询转换为其他查询。

像这样的查询:

 SELECT * FROM mytable WHERE col1 > @value1 OR col2 < @value2 

可以转化为:

 SELECT * FROM mytable WHERE col1 > @value1 UNION SELECT * FROM mytable WHERE col2 < @value2 

或这个:

 SELECT mo.* FROM ( SELECT id FROM mytable WHERE col1 > @value1 UNION SELECT id FROM mytable WHERE col2 < @value2 ) mi JOIN mytable mo ON mo.id = mi.id 

,看起来丑陋,但可以产生更好的执行计划。

最常见的事情之一就是replace这个查询:

 SELECT * FROM mytable WHERE col IN ( SELECT othercol FROM othertable ) 

与这一个:

 SELECT * FROM mytable mo WHERE EXISTS ( SELECT NULL FROM othertable o WHERE o.othercol = mo.col ) 

在一些RDBMS (如PostgreSQL )中, DISTINCTGROUP BY使用不同的执行计划,所以有时最好用另一个replace:

 SELECT mo.grouper, ( SELECT SUM(col) FROM mytable mi WHERE mi.grouper = mo.grouper ) FROM ( SELECT DISTINCT grouper FROM mytable ) mo 

 SELECT mo.grouper, SUM(col) FROM mytable GROUP BY mo.grouper 

PostgreSQLDISTINCTsorting和GROUP BY哈希。

MySQL缺lessFULL OUTER JOIN ,因此可以将其重写为:

 SELECT t1.col1, t2.col2 FROM table1 t1 LEFT OUTER JOIN table2 t2 ON t1.id = t2.id 

 SELECT t1.col1, t2.col2 FROM table1 t1 LEFT JOIN table2 t2 ON t1.id = t2.id UNION ALL SELECT NULL, t2.col2 FROM table1 t1 RIGHT JOIN table2 t2 ON t1.id = t2.id WHERE t1.id IS NULL 

,但在我的博客中看到如何在MySQL更高效地执行此操作:

  • 在MySQL中模拟FULL OUTER JOIN

Oracle这种分层查询:

 SELECT DISTINCT(animal_id) AS animal_id FROM animal START WITH animal_id = :id CONNECT BY PRIOR animal_id IN (father, mother) ORDER BY animal_id 

可以转化为:

 SELECT DISTINCT(animal_id) AS animal_id FROM ( SELECT 0 AS gender, animal_id, father AS parent FROM animal UNION ALL SELECT 1, animal_id, mother FROM animal ) START WITH animal_id = :id CONNECT BY parent = PRIOR animal_id ORDER BY animal_id 

后者更高效。

请参阅我的博客文章中的执行计划详细信息:

  • 族谱查询父母双方

要查找与给定范围重叠的所有范围,可以使用以下查询:

 SELECT * FROM ranges WHERE end_date >= @start AND start_date <= @end 

,但在SQL Server这个更复杂的查询可以更快地得到相同的结果:

 SELECT * FROM ranges WHERE (start_date > @start AND start_date <= @end) OR (@start BETWEEN start_date AND end_date) 

,信不信由你,我在我的博客上也有一篇文章:

  • 重叠范围:SQL Server

SQL Server也缺乏一个有效的方式来做累积聚合,所以这个查询:

 SELECT mi.id, SUM(mo.value) AS running_sum FROM mytable mi JOIN mytable mo ON mo.id <= mi.id GROUP BY mi.id 

可以更有效地使用,Lord帮助我,游标(你听到我的权利: cursorsmore efficientlySQL Server在一个句子)重写。

在我的博客中看到这篇文章如何做到这一点:

  • 压扁时间跨度:SQL Server

在金融应用程序中通常会遇到某种types的查询,这些查询会search货币的有效汇率,例如Oracle这种查询:

 SELECT TO_CHAR(SUM(xac_amount * rte_rate), 'FM999G999G999G999G999G999D999999') FROM t_transaction x JOIN t_rate r ON (rte_currency, rte_date) IN ( SELECT xac_currency, MAX(rte_date) FROM t_rate WHERE rte_currency = xac_currency AND rte_date <= xac_date ) 

这个查询可以被大量地重写为使用一个相等的条件,它允许一个HASH JOIN而不是NESTED LOOPS

 WITH v_rate AS ( SELECT cur_id AS eff_currency, dte_date AS eff_date, rte_rate AS eff_rate FROM ( SELECT cur_id, dte_date, ( SELECT MAX(rte_date) FROM t_rate ri WHERE rte_currency = cur_id AND rte_date <= dte_date ) AS rte_effdate FROM ( SELECT ( SELECT MAX(rte_date) FROM t_rate ) - level + 1 AS dte_date FROM dual CONNECT BY level <= ( SELECT MAX(rte_date) - MIN(rte_date) FROM t_rate ) ) v_date, ( SELECT 1 AS cur_id FROM dual UNION ALL SELECT 2 AS cur_id FROM dual ) v_currency ) v_eff LEFT JOIN t_rate ON rte_currency = cur_id AND rte_date = rte_effdate ) SELECT TO_CHAR(SUM(xac_amount * eff_rate), 'FM999G999G999G999G999G999D999999') FROM ( SELECT xac_currency, TRUNC(xac_date) AS xac_date, SUM(xac_amount) AS xac_amount, COUNT(*) AS cnt FROM t_transaction x GROUP BY xac_currency, TRUNC(xac_date) ) JOIN v_rate ON eff_currency = xac_currency AND eff_date = xac_date 

尽pipe体积很大,但后者的查询速度要快6倍。

这里的主要思想是用<=replace<= ,这需要构build内存日历表。 JOIN

  • 转换货币

这里有一些与Oracle 8和9一起工作(当然,有时做相反的事情可能会使查询更简单或更快):

括号可以被删除,如果它们不被用来覆盖运算符的优先级。 一个简单的例子是当where子句中的所有布尔运算符是相同的: where ((a or b) or c)等价于where a or b or c

子查询通常可以(如果不是总是) 与主查询合并以简化它。 根据我的经验,这通常会大大提高性能:

 select foo.a, bar.a from foomatic foo, bartastic bar where foo.id = bar.id and bar.id = ( select ban.id from bantabulous ban where ban.bandana = 42 ) ; 

相当于

 select foo.a, bar.a from foomatic foo, bartastic bar, bantabulous ban where foo.id = bar.id and bar.id = ban.id and ban.bandana = 42 ; 

使用ANSI连接将很多“代码猴子”逻辑从where子句的真正有趣的部分中分离出来:以前的查询相当于

 select foo.a, bar.a from foomatic foo join bartastic bar on bar.id = foo.id join bantabulous ban on ban.id = bar.id where ban.bandana = 42 ; 

如果要检查是否存在一行,请不要使用count(*) ,而应使用rownum = 1或将查询放在where exists子句中,以仅提取一行而不是全部。

  • 我想最明显的是寻找任何游标,可以用一个基于SQL“Set”的操作replace。
  • 接下来在我的列表中,查找可以重写为不相关查询的任何相关子查询
  • 在长存储过程中,将单独的SQL语句分解为它们自己的存储过程。 这样他们将得到那里自己的caching查询计划。
  • 寻找可以缩短范围的交易。 我经常在一个可以安全地在外面的交易中find陈述。
  • 子select通常可以被重写为直接连接(现代优化器擅长于发现简单的连接)

正如@Quassnoi提到的那样,Optimiser经常做得很好。 帮助它的一种方法是确保索引和统计信息是最新的,并且为您的查询工作负载提供适当的索引。

我喜欢通过连接查询来replace所有types的子查询。

这一点很明显:

 SELECT * FROM mytable mo WHERE EXISTS ( SELECT * FROM othertable o WHERE o.othercol = mo.col ) 

通过

 SELECT mo.* FROM mytable mo inner join othertable o on o.othercol = mo.col 

而这个估计是:

 SELECT * FROM mytable mo WHERE NOT EXISTS ( SELECT * FROM othertable o WHERE o.othercol = mo.col ) 

通过

 SELECT mo.* FROM mytable mo left outer join othertable o on o.othercol = mo.col WHERE o.othercol is null 

它可以帮助DBMS在一个大的请求中select好的执行计划。

我喜欢团队中的每个人遵循一套标准,使代码易读,可维护,易于理解,可清洗等。

  • 每个人都使用相同的别名
  • 没有游标。 没有循环
  • 为什么甚至想到IN的时候可以EXISTS
  • INDENT
  • 一致的编码风格

这里有更多的东西什么是你最有用的数据库标准?

鉴于SQL的本质,您绝对必须意识到任何重构的性能影响。 重构SQL应用程序是一个很好的重构资源,重点是性能(见第5章)。

尽pipe简化可能并不等于优化,但在编写可读的SQL代码时,简化是非常重要的,这对于能够检查SQL代码是否符合概念正确性(而不是语法正确性,开发环境应该为您检查)至关重要。 在我看来,在一个理想的世界里,我们会编写最简单,可读的SQL代码,然后优化器将重写SQL代码以任何forms(也许更详细)运行速度最快。

我发现把SQL语句看作基于set逻辑的思想是非常有用的,特别是当我需要将where子句或者where子句的复杂否定结合起来的时候。 在这种情况下,我使用布尔代数的定律 。

对于简化where子句最重要的可能是DeMorgan法则(注意“·”是“AND”,“+”是“OR”):

  • NOT(x·y)= NOT x + NOT y
  • NOT(x + y)= NOT x·NOT y

这在SQL中转换为:

 NOT (expr1 AND expr2) -> NOT expr1 OR NOT expr2 NOT (expr1 OR expr2) -> NOT expr1 AND NOT expr2 

这些规则可以在简化含有大量嵌套的ANDOR部分的子句中非常有用。

记住field1 IN (value1, value2, ...)等于field1 = value1 OR field1 = value2 OR ...也是有用的。 这可以让你取消IN ()两种方法之一:

 NOT field1 IN (value1, value2) -- for longer lists NOT field1 = value1 AND NOT field1 = value2 -- for shorter lists 

子查询也可以这样想。 例如,这个否定的where子句:

 NOT (table1.field1 = value1 AND EXISTS (SELECT * FROM table2 WHERE table1.field1 = table2.field2)) 

可以改写为:

 NOT table1.field1 = value1 OR NOT EXISTS (SELECT * FROM table2 WHERE table1.field1 = table2.field2)) 

这些法则不会告诉你如何将使用子查询的SQL查询转换为使用连接的SQL查询,但布尔逻辑可以帮助您了解连接types以及查询应该返回的内容。 例如,对于表ABINNER JOIN类似于A AND BLEFT OUTER JOIN类似于(A AND NOT B) OR (A AND B) ,简化为A OR (A AND B) ,而FULL OUTER JOINA OR (A AND B) OR B ,简化为A OR B

我的方法是学习一般的关系理论和特别的关系代数。 然后学会发现SQL中使用的构造来实现关系代数(例如通用量化aka division)和微积分(例如存在量化)的运算符。 问题在于SQL具有在关系模型中找不到的特征,例如空值,这些特征可能是最好的重构。 推荐阅读: SQL和关系理论:如何编写准确的SQL代码By CJ Date 。

在这方面,我不相信“大多数SUBSELECT可以改写为JOIN”的事实代表了一种简化。

以这个查询为例:

 SELECT c FROM T1 WHERE c NOT IN ( SELECT c FROM T2 ); 

用JOIN重写

 SELECT DISTINCT T1.c FROM T1 NATURAL LEFT OUTER JOIN T2 WHERE T2.c IS NULL; 

join更详细!

或者,承认构造正在实施c的投影上的抗连接,例如伪阿尔戈布拉

 T1 { c } antijoin T2 { c } 

简化使用关系运算符:

 SELECT c FROM T1 EXCEPT SELECT c FROM T2; 
Interesting Posts